4 * An implementation of RFC821 (Simple Mail Transfer Protocol) for the
17 #include <sys/types.h>
19 #if TIME_WITH_SYS_TIME
20 # include <sys/time.h>
24 # include <sys/time.h>
36 #include "sysdep_decls.h"
37 #include "citserver.h"
41 #include "dynloader.h"
48 #include "internet_addressing.h"
51 #include "clientsocket.h"
58 struct citsmtp { /* Information about the current session */
61 struct usersupp vrfy_buffer;
66 int number_of_recipients;
69 int message_originated_locally;
72 enum { /* Command states for login authentication */
78 enum { /* Delivery modes */
83 #define SMTP ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
84 #define SMTP_RECPS ((char *)CtdlGetUserData(SYM_SMTP_RECPS))
85 #define SMTP_ROOMS ((char *)CtdlGetUserData(SYM_SMTP_ROOMS))
91 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
95 /*****************************************************************************/
96 /* SMTP SERVER (INBOUND) STUFF */
97 /*****************************************************************************/
103 * Here's where our SMTP session begins its happy day.
105 void smtp_greeting(void) {
107 strcpy(CC->cs_clientname, "SMTP session");
108 CC->internal_pgm = 1;
109 CC->cs_flags |= CS_STEALTH;
110 CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
111 CtdlAllocUserData(SYM_SMTP_RECPS, SIZ);
112 CtdlAllocUserData(SYM_SMTP_ROOMS, SIZ);
113 snprintf(SMTP_RECPS, SIZ, "%s", "");
114 snprintf(SMTP_ROOMS, SIZ, "%s", "");
116 cprintf("220 %s ESMTP Citadel/UX server ready.\r\n", config.c_fqdn);
121 * Implement HELO and EHLO commands.
123 void smtp_hello(char *argbuf, int is_esmtp) {
125 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
128 cprintf("250 Greetings and joyous salutations.\r\n");
131 cprintf("250-Greetings and joyous salutations.\r\n");
132 cprintf("250-HELP\r\n");
133 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
134 cprintf("250-PIPELINING\r\n");
135 cprintf("250 AUTH=LOGIN\r\n");
141 * Implement HELP command.
143 void smtp_help(void) {
144 cprintf("214-Commands accepted:\r\n");
145 cprintf("214- DATA\r\n");
146 cprintf("214- EHLO\r\n");
147 cprintf("214- EXPN\r\n");
148 cprintf("214- HELO\r\n");
149 cprintf("214- HELP\r\n");
150 cprintf("214- MAIL\r\n");
151 cprintf("214- NOOP\r\n");
152 cprintf("214- QUIT\r\n");
153 cprintf("214- RCPT\r\n");
154 cprintf("214- RSET\r\n");
155 cprintf("214- VRFY\r\n");
163 void smtp_get_user(char *argbuf) {
167 decode_base64(username, argbuf, SIZ);
168 lprintf(9, "Trying <%s>\n", username);
169 if (CtdlLoginExistingUser(username) == login_ok) {
170 encode_base64(buf, "Password:");
171 cprintf("334 %s\r\n", buf);
172 SMTP->command_state = smtp_password;
175 cprintf("500 No such user.\r\n");
176 SMTP->command_state = smtp_command;
184 void smtp_get_pass(char *argbuf) {
187 decode_base64(password, argbuf, SIZ);
188 lprintf(9, "Trying <%s>\n", password);
189 if (CtdlTryPassword(password) == pass_ok) {
190 cprintf("235 Hello, %s\r\n", CC->usersupp.fullname);
191 lprintf(9, "SMTP authenticated login successful\n");
192 CC->internal_pgm = 0;
193 CC->cs_flags &= ~CS_STEALTH;
196 cprintf("500 Authentication failed.\r\n");
198 SMTP->command_state = smtp_command;
205 void smtp_auth(char *argbuf) {
208 if (strncasecmp(argbuf, "login", 5) ) {
209 cprintf("550 We only support LOGIN authentication.\r\n");
213 if (strlen(argbuf) >= 7) {
214 smtp_get_user(&argbuf[6]);
218 encode_base64(buf, "Username:");
219 cprintf("334 %s\r\n", buf);
220 SMTP->command_state = smtp_user;
226 * Back end for smtp_vrfy() command
228 void smtp_vrfy_backend(struct usersupp *us, void *data) {
230 if (!fuzzy_match(us, SMTP->vrfy_match)) {
232 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
238 * Implements the VRFY (verify user name) command.
239 * Performs fuzzy match on full user names.
241 void smtp_vrfy(char *argbuf) {
242 SMTP->vrfy_count = 0;
243 strcpy(SMTP->vrfy_match, argbuf);
244 ForEachUser(smtp_vrfy_backend, NULL);
246 if (SMTP->vrfy_count < 1) {
247 cprintf("550 String does not match anything.\r\n");
249 else if (SMTP->vrfy_count == 1) {
250 cprintf("250 %s <cit%ld@%s>\r\n",
251 SMTP->vrfy_buffer.fullname,
252 SMTP->vrfy_buffer.usernum,
255 else if (SMTP->vrfy_count > 1) {
256 cprintf("553 Request ambiguous: %d users matched.\r\n",
265 * Back end for smtp_expn() command
267 void smtp_expn_backend(struct usersupp *us, void *data) {
269 if (!fuzzy_match(us, SMTP->vrfy_match)) {
271 if (SMTP->vrfy_count >= 1) {
272 cprintf("250-%s <cit%ld@%s>\r\n",
273 SMTP->vrfy_buffer.fullname,
274 SMTP->vrfy_buffer.usernum,
279 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
285 * Implements the EXPN (expand user name) command.
286 * Performs fuzzy match on full user names.
288 void smtp_expn(char *argbuf) {
289 SMTP->vrfy_count = 0;
290 strcpy(SMTP->vrfy_match, argbuf);
291 ForEachUser(smtp_expn_backend, NULL);
293 if (SMTP->vrfy_count < 1) {
294 cprintf("550 String does not match anything.\r\n");
296 else if (SMTP->vrfy_count >= 1) {
297 cprintf("250 %s <cit%ld@%s>\r\n",
298 SMTP->vrfy_buffer.fullname,
299 SMTP->vrfy_buffer.usernum,
306 * Implements the RSET (reset state) command.
307 * Currently this just zeroes out the state buffer. If pointers to data
308 * allocated with mallok() are ever placed in the state buffer, we have to
309 * be sure to phree() them first!
311 void smtp_rset(void) {
312 memset(SMTP, 0, sizeof(struct citsmtp));
316 cprintf("250 Zap!\r\n");
320 * Clear out the portions of the state buffer that need to be cleared out
321 * after the DATA command finishes.
323 void smtp_data_clear(void) {
324 strcpy(SMTP->from, "");
325 strcpy(SMTP->recipients, "");
326 SMTP->number_of_recipients = 0;
327 SMTP->delivery_mode = 0;
328 SMTP->message_originated_locally = 0;
334 * Implements the "MAIL From:" command
336 void smtp_mail(char *argbuf) {
340 struct recptypes *valid;
342 if (strlen(SMTP->from) != 0) {
343 cprintf("503 Only one sender permitted\r\n");
347 if (strncasecmp(argbuf, "From:", 5)) {
348 cprintf("501 Syntax error\r\n");
352 strcpy(SMTP->from, &argbuf[5]);
354 stripallbut(SMTP->from, '<', '>');
356 if (strlen(SMTP->from) == 0) {
357 cprintf("501 Empty sender name is not permitted\r\n");
361 /* If this SMTP connection is from a logged-in user, make sure that
362 * the user only sends email from his/her own address.
365 valid = validate_recipients(SMTP->from);
366 if ( (valid->num_local == 1) &&
367 (!strcasecmp(valid->recp_local, CC->usersupp.fullname)) ) {
368 cprintf("250 Sender ok <%s>\r\n", valid->recp_local);
369 SMTP->message_originated_locally = 1;
372 cprintf("550 <%s> is not your address.\r\n",
374 strcpy(SMTP->from, "");
381 /* Otherwise, make sure outsiders aren't trying to forge mail from
385 process_rfc822_addr(SMTP->from, user, node, name);
386 if (CtdlHostAlias(node) != hostalias_nomatch) {
387 cprintf("550 You must log in to send mail from %s\r\n",
389 strcpy(SMTP->from, "");
394 cprintf("250 Sender ok\r\n");
400 * Implements the "RCPT To:" command
402 void smtp_rcpt(char *argbuf) {
404 struct recptypes *valid;
406 if (strlen(SMTP->from) == 0) {
407 cprintf("503 Need MAIL before RCPT\r\n");
411 if (strncasecmp(argbuf, "To:", 3)) {
412 cprintf("501 Syntax error\r\n");
416 strcpy(recp, &argbuf[3]);
418 stripallbut(recp, '<', '>');
420 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
421 cprintf("452 Too many recipients\r\n");
425 valid = validate_recipients(recp);
426 if (valid->num_error > 0) {
427 cprintf("599 Error: %s\r\n", valid->errormsg);
432 if (valid->num_internet > 0) {
433 if (SMTP->message_originated_locally == 0) {
434 cprintf("551 Relaying denied <%s>\r\n", recp);
440 cprintf("250 RCPT ok <%s>\r\n", recp);
441 if (strlen(SMTP->recipients) > 0) {
442 strcat(SMTP->recipients, ",");
444 strcat(SMTP->recipients, recp);
445 SMTP->number_of_recipients += 1;
452 * Implements the DATA command
454 void smtp_data(void) {
456 struct CtdlMessage *msg;
459 struct recptypes *valid;
462 if (strlen(SMTP->from) == 0) {
463 cprintf("503 Need MAIL command first.\r\n");
467 if (SMTP->number_of_recipients < 1) {
468 cprintf("503 Need RCPT command first.\r\n");
472 cprintf("354 Transmit message now; terminate with '.' by itself\r\n");
474 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
478 it should be Received: from %s (real.name.dom [w.x.y.z])
480 if (body != NULL) snprintf(body, 4096,
481 "Received: from %s (%s)\n"
488 body = CtdlReadMessageBody(".", config.c_maxmsglen, body);
490 cprintf("550 Unable to save message: internal error.\r\n");
494 lprintf(9, "Converting message...\n");
495 msg = convert_internet_message(body);
497 /* If the user is locally authenticated, FORCE the From: header to
498 * show up as the real sender. Yes, this violates the RFC standard,
499 * but IT MAKES SENSE. Comment it out if you don't like this behavior.
502 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
503 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
504 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
505 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
506 msg->cm_fields['N'] = strdoop(config.c_nodename);
507 msg->cm_fields['H'] = strdoop(config.c_humannode);
510 /* Submit the message into the Citadel system. */
511 valid = validate_recipients(SMTP->recipients);
513 /* If there are modules that want to scan this message before final
514 * submission (such as virus checkers or spam filters), call them now
515 * and give them an opportunity to reject the message.
517 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
519 if (scan_errors > 0) { /* We don't want this message! */
521 if (msg->cm_fields['0'] == NULL) {
522 msg->cm_fields['0'] = strdoop(
523 "Message rejected by filter");
526 cprintf("552 %s\r\n", msg->cm_fields['0']);
529 else { /* Ok, we'll accept this message. */
530 msgnum = CtdlSubmitMsg(msg, valid, "");
532 cprintf("250 Message accepted.\r\n");
535 cprintf("550 Internal delivery error\r\n");
539 CtdlFreeMessage(msg);
541 smtp_data_clear(); /* clear out the buffers now */
548 * Main command loop for SMTP sessions.
550 void smtp_command_loop(void) {
554 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
555 if (client_gets(cmdbuf) < 1) {
556 lprintf(3, "SMTP socket is broken. Ending session.\n");
560 lprintf(5, "SMTP: %s\n", cmdbuf);
561 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
563 if (SMTP->command_state == smtp_user) {
564 smtp_get_user(cmdbuf);
567 else if (SMTP->command_state == smtp_password) {
568 smtp_get_pass(cmdbuf);
571 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
572 smtp_auth(&cmdbuf[5]);
575 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
579 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
580 smtp_hello(&cmdbuf[5], 1);
583 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
584 smtp_expn(&cmdbuf[5]);
587 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
588 smtp_hello(&cmdbuf[5], 0);
591 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
595 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
596 smtp_mail(&cmdbuf[5]);
599 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
600 cprintf("250 NOOP\r\n");
603 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
604 cprintf("221 Goodbye...\r\n");
609 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
610 smtp_rcpt(&cmdbuf[5]);
613 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
617 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
618 smtp_vrfy(&cmdbuf[5]);
622 cprintf("502 I'm afraid I can't do that.\r\n");
630 /*****************************************************************************/
631 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
632 /*****************************************************************************/
639 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
642 void smtp_try(const char *key, const char *addr, int *status,
643 char *dsn, size_t n, long msgnum)
650 char user[SIZ], node[SIZ], name[SIZ];
656 size_t blocksize = 0;
659 /* Parse out the host portion of the recipient address */
660 process_rfc822_addr(addr, user, node, name);
662 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
665 /* Load the message out of the database into a temp file */
667 if (msg_fp == NULL) {
669 snprintf(dsn, n, "Error creating temporary file");
673 CtdlRedirectOutput(msg_fp, -1);
674 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
675 CtdlRedirectOutput(NULL, -1);
676 fseek(msg_fp, 0L, SEEK_END);
677 msg_size = ftell(msg_fp);
681 /* Extract something to send later in the 'MAIL From:' command */
682 strcpy(mailfrom, "");
686 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
687 if (!strncasecmp(buf, "From:", 5)) {
688 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
690 for (i=0; i<strlen(mailfrom); ++i) {
691 if (!isprint(mailfrom[i])) {
692 strcpy(&mailfrom[i], &mailfrom[i+1]);
697 /* Strip out parenthesized names */
700 for (i=0; i<strlen(mailfrom); ++i) {
701 if (mailfrom[i] == '(') lp = i;
702 if (mailfrom[i] == ')') rp = i;
704 if ((lp>0)&&(rp>lp)) {
705 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
708 /* Prefer brokketized names */
711 for (i=0; i<strlen(mailfrom); ++i) {
712 if (mailfrom[i] == '<') lp = i;
713 if (mailfrom[i] == '>') rp = i;
715 if ( (lp>=0) && (rp>lp) ) {
717 strcpy(mailfrom, &mailfrom[lp]);
722 } while (scan_done == 0);
723 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
725 /* Figure out what mail exchanger host we have to connect to */
726 num_mxhosts = getmx(mxhosts, node);
727 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
728 if (num_mxhosts < 1) {
730 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
734 for (mx=0; mx<num_mxhosts; ++mx) {
735 extract(buf, mxhosts, mx);
736 lprintf(9, "Trying <%s>\n", buf);
737 sock = sock_connect(buf, "25", "tcp");
738 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
739 if (sock >= 0) lprintf(9, "Connected!\n");
740 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
741 if (sock >= 0) break;
745 *status = 4; /* dsn is already filled in */
749 /* Process the SMTP greeting from the server */
750 if (ml_sock_gets(sock, buf) < 0) {
752 strcpy(dsn, "Connection broken during SMTP conversation");
755 lprintf(9, "<%s\n", buf);
759 safestrncpy(dsn, &buf[4], 1023);
764 safestrncpy(dsn, &buf[4], 1023);
769 /* At this point we know we are talking to a real SMTP server */
771 /* Do a HELO command */
772 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
773 lprintf(9, ">%s", buf);
774 sock_write(sock, buf, strlen(buf));
775 if (ml_sock_gets(sock, buf) < 0) {
777 strcpy(dsn, "Connection broken during SMTP HELO");
780 lprintf(9, "<%s\n", buf);
784 safestrncpy(dsn, &buf[4], 1023);
789 safestrncpy(dsn, &buf[4], 1023);
795 /* HELO succeeded, now try the MAIL From: command */
796 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
797 lprintf(9, ">%s", buf);
798 sock_write(sock, buf, strlen(buf));
799 if (ml_sock_gets(sock, buf) < 0) {
801 strcpy(dsn, "Connection broken during SMTP MAIL");
804 lprintf(9, "<%s\n", buf);
808 safestrncpy(dsn, &buf[4], 1023);
813 safestrncpy(dsn, &buf[4], 1023);
819 /* MAIL succeeded, now try the RCPT To: command */
820 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
821 lprintf(9, ">%s", buf);
822 sock_write(sock, buf, strlen(buf));
823 if (ml_sock_gets(sock, buf) < 0) {
825 strcpy(dsn, "Connection broken during SMTP RCPT");
828 lprintf(9, "<%s\n", buf);
832 safestrncpy(dsn, &buf[4], 1023);
837 safestrncpy(dsn, &buf[4], 1023);
843 /* RCPT succeeded, now try the DATA command */
844 lprintf(9, ">DATA\n");
845 sock_write(sock, "DATA\r\n", 6);
846 if (ml_sock_gets(sock, buf) < 0) {
848 strcpy(dsn, "Connection broken during SMTP DATA");
851 lprintf(9, "<%s\n", buf);
855 safestrncpy(dsn, &buf[4], 1023);
860 safestrncpy(dsn, &buf[4], 1023);
865 /* If we reach this point, the server is expecting data */
867 while (msg_size > 0) {
868 blocksize = sizeof(buf);
869 if (blocksize > msg_size) blocksize = msg_size;
870 fread(buf, blocksize, 1, msg_fp);
871 sock_write(sock, buf, blocksize);
872 msg_size -= blocksize;
874 if (buf[blocksize-1] != 10) {
875 lprintf(5, "Possible problem: message did not correctly "
876 "terminate. (expecting 0x10, got 0x%02x)\n",
880 sock_write(sock, ".\r\n", 3);
881 if (ml_sock_gets(sock, buf) < 0) {
883 strcpy(dsn, "Connection broken during SMTP message transmit");
886 lprintf(9, "%s\n", buf);
890 safestrncpy(dsn, &buf[4], 1023);
895 safestrncpy(dsn, &buf[4], 1023);
901 safestrncpy(dsn, &buf[4], 1023);
904 lprintf(9, ">QUIT\n");
905 sock_write(sock, "QUIT\r\n", 6);
906 ml_sock_gets(sock, buf);
907 lprintf(9, "<%s\n", buf);
909 bail: if (msg_fp != NULL) fclose(msg_fp);
917 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
918 * instructions for "5" codes (permanent fatal errors) and produce/deliver
919 * a "bounce" message (delivery status notification).
921 void smtp_do_bounce(char *instr) {
932 long bounce_msgid = (-1);
933 time_t submitted = 0L;
934 struct CtdlMessage *bmsg = NULL;
936 struct recptypes *valid;
937 int successful_bounce = 0;
939 lprintf(9, "smtp_do_bounce() called\n");
940 strcpy(bounceto, "");
942 lines = num_tokens(instr, '\n');
945 /* See if it's time to give up on delivery of this message */
946 for (i=0; i<lines; ++i) {
947 extract_token(buf, instr, i, '\n');
948 extract(key, buf, 0);
949 extract(addr, buf, 1);
950 if (!strcasecmp(key, "submitted")) {
951 submitted = atol(addr);
955 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
961 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
962 if (bmsg == NULL) return;
963 memset(bmsg, 0, sizeof(struct CtdlMessage));
965 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
966 bmsg->cm_anon_type = MES_NORMAL;
967 bmsg->cm_format_type = 1;
968 bmsg->cm_fields['A'] = strdoop("Citadel");
969 bmsg->cm_fields['O'] = strdoop(MAILROOM);
970 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
972 if (give_up) bmsg->cm_fields['M'] = strdoop(
973 "A message you sent could not be delivered to some or all of its recipients\n"
974 "due to prolonged unavailability of its destination(s).\n"
975 "Giving up on the following addresses:\n\n"
978 else bmsg->cm_fields['M'] = strdoop(
979 "A message you sent could not be delivered to some or all of its recipients.\n"
980 "The following addresses were undeliverable:\n\n"
984 * Now go through the instructions checking for stuff.
987 for (i=0; i<lines; ++i) {
988 extract_token(buf, instr, i, '\n');
989 extract(key, buf, 0);
990 extract(addr, buf, 1);
991 status = extract_int(buf, 2);
992 extract(dsn, buf, 3);
995 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
996 key, addr, status, dsn);
998 if (!strcasecmp(key, "bounceto")) {
999 strcpy(bounceto, addr);
1003 (!strcasecmp(key, "local"))
1004 || (!strcasecmp(key, "remote"))
1005 || (!strcasecmp(key, "ignet"))
1006 || (!strcasecmp(key, "room"))
1008 if (status == 5) bounce_this = 1;
1009 if (give_up) bounce_this = 1;
1015 if (bmsg->cm_fields['M'] == NULL) {
1016 lprintf(2, "ERROR ... M field is null "
1017 "(%s:%d)\n", __FILE__, __LINE__);
1020 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1021 strlen(bmsg->cm_fields['M']) + 1024 );
1022 strcat(bmsg->cm_fields['M'], addr);
1023 strcat(bmsg->cm_fields['M'], ": ");
1024 strcat(bmsg->cm_fields['M'], dsn);
1025 strcat(bmsg->cm_fields['M'], "\n");
1027 remove_token(instr, i, '\n');
1033 /* Deliver the bounce if there's anything worth mentioning */
1034 lprintf(9, "num_bounces = %d\n", num_bounces);
1035 if (num_bounces > 0) {
1037 /* First try the user who sent the message */
1038 lprintf(9, "bounce to user? <%s>\n", bounceto);
1039 if (strlen(bounceto) == 0) {
1040 lprintf(7, "No bounce address specified\n");
1041 bounce_msgid = (-1L);
1044 /* Can we deliver the bounce to the original sender? */
1045 valid = validate_recipients(bounceto);
1046 if (valid != NULL) {
1047 if (valid->num_error == 0) {
1048 CtdlSubmitMsg(bmsg, valid, "");
1049 successful_bounce = 1;
1053 /* If not, post it in the Aide> room */
1054 if (successful_bounce == 0) {
1055 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1058 /* Free up the memory we used */
1059 if (valid != NULL) {
1064 CtdlFreeMessage(bmsg);
1065 lprintf(9, "Done processing bounces\n");
1070 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1071 * set of delivery instructions for completed deliveries and remove them.
1073 * It returns the number of incomplete deliveries remaining.
1075 int smtp_purge_completed_deliveries(char *instr) {
1086 lines = num_tokens(instr, '\n');
1087 for (i=0; i<lines; ++i) {
1088 extract_token(buf, instr, i, '\n');
1089 extract(key, buf, 0);
1090 extract(addr, buf, 1);
1091 status = extract_int(buf, 2);
1092 extract(dsn, buf, 3);
1097 (!strcasecmp(key, "local"))
1098 || (!strcasecmp(key, "remote"))
1099 || (!strcasecmp(key, "ignet"))
1100 || (!strcasecmp(key, "room"))
1102 if (status == 2) completed = 1;
1107 remove_token(instr, i, '\n');
1120 * Called by smtp_do_queue() to handle an individual message.
1122 void smtp_do_procmsg(long msgnum, void *userdata) {
1123 struct CtdlMessage *msg;
1125 char *results = NULL;
1133 long text_msgid = (-1);
1134 int incomplete_deliveries_remaining;
1135 time_t attempted = 0L;
1136 time_t last_attempted = 0L;
1137 time_t retry = SMTP_RETRY_INTERVAL;
1139 lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1141 msg = CtdlFetchMessage(msgnum);
1143 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1147 instr = strdoop(msg->cm_fields['M']);
1148 CtdlFreeMessage(msg);
1150 /* Strip out the headers amd any other non-instruction line */
1151 lines = num_tokens(instr, '\n');
1152 for (i=0; i<lines; ++i) {
1153 extract_token(buf, instr, i, '\n');
1154 if (num_tokens(buf, '|') < 2) {
1155 remove_token(instr, i, '\n');
1161 /* Learn the message ID and find out about recent delivery attempts */
1162 lines = num_tokens(instr, '\n');
1163 for (i=0; i<lines; ++i) {
1164 extract_token(buf, instr, i, '\n');
1165 extract(key, buf, 0);
1166 if (!strcasecmp(key, "msgid")) {
1167 text_msgid = extract_long(buf, 1);
1169 if (!strcasecmp(key, "retry")) {
1170 /* double the retry interval after each attempt */
1171 retry = extract_long(buf, 1) * 2L;
1172 if (retry > SMTP_RETRY_MAX) {
1173 retry = SMTP_RETRY_MAX;
1175 remove_token(instr, i, '\n');
1177 if (!strcasecmp(key, "attempted")) {
1178 attempted = extract_long(buf, 1);
1179 if (attempted > last_attempted)
1180 last_attempted = attempted;
1185 * Postpone delivery if we've already tried recently.
1187 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1188 lprintf(7, "Retry time not yet reached.\n");
1195 * Bail out if there's no actual message associated with this
1197 if (text_msgid < 0L) {
1198 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1203 /* Plow through the instructions looking for 'remote' directives and
1204 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1205 * were experienced and it's time to try again)
1207 lines = num_tokens(instr, '\n');
1208 for (i=0; i<lines; ++i) {
1209 extract_token(buf, instr, i, '\n');
1210 extract(key, buf, 0);
1211 extract(addr, buf, 1);
1212 status = extract_int(buf, 2);
1213 extract(dsn, buf, 3);
1214 if ( (!strcasecmp(key, "remote"))
1215 && ((status==0)||(status==3)||(status==4)) ) {
1216 remove_token(instr, i, '\n');
1219 lprintf(9, "SMTP: Trying <%s>\n", addr);
1220 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1222 if (results == NULL) {
1223 results = mallok(1024);
1224 memset(results, 0, 1024);
1227 results = reallok(results,
1228 strlen(results) + 1024);
1230 snprintf(&results[strlen(results)], 1024,
1232 key, addr, status, dsn);
1237 if (results != NULL) {
1238 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1239 strcat(instr, results);
1244 /* Generate 'bounce' messages */
1245 smtp_do_bounce(instr);
1247 /* Go through the delivery list, deleting completed deliveries */
1248 incomplete_deliveries_remaining =
1249 smtp_purge_completed_deliveries(instr);
1253 * No delivery instructions remain, so delete both the instructions
1254 * message and the message message.
1256 if (incomplete_deliveries_remaining <= 0) {
1257 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1258 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1263 * Uncompleted delivery instructions remain, so delete the old
1264 * instructions and replace with the updated ones.
1266 if (incomplete_deliveries_remaining > 0) {
1267 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1268 msg = mallok(sizeof(struct CtdlMessage));
1269 memset(msg, 0, sizeof(struct CtdlMessage));
1270 msg->cm_magic = CTDLMESSAGE_MAGIC;
1271 msg->cm_anon_type = MES_NORMAL;
1272 msg->cm_format_type = FMT_RFC822;
1273 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1274 snprintf(msg->cm_fields['M'],
1276 "Content-type: %s\n\n%s\n"
1279 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1281 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1282 CtdlFreeMessage(msg);
1292 * Run through the queue sending out messages.
1294 void smtp_do_queue(void) {
1295 static int doing_queue = 0;
1298 * This is a simple concurrency check to make sure only one queue run
1299 * is done at a time. We could do this with a mutex, but since we
1300 * don't really require extremely fine granularity here, we'll do it
1301 * with a static variable instead.
1303 if (doing_queue) return;
1307 * Go ahead and run the queue
1309 lprintf(7, "SMTP: processing outbound queue\n");
1311 if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1312 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1315 CtdlForEachMessage(MSGS_ALL, 0L,
1316 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1318 lprintf(7, "SMTP: queue run completed\n");
1325 /*****************************************************************************/
1326 /* SMTP UTILITY COMMANDS */
1327 /*****************************************************************************/
1329 void cmd_smtp(char *argbuf) {
1336 if (CtdlAccessCheck(ac_aide)) return;
1338 extract(cmd, argbuf, 0);
1340 if (!strcasecmp(cmd, "mx")) {
1341 extract(node, argbuf, 1);
1342 num_mxhosts = getmx(buf, node);
1343 cprintf("%d %d MX hosts listed for %s\n",
1344 LISTING_FOLLOWS, num_mxhosts, node);
1345 for (i=0; i<num_mxhosts; ++i) {
1346 extract(node, buf, i);
1347 cprintf("%s\n", node);
1353 else if (!strcasecmp(cmd, "runqueue")) {
1355 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1360 cprintf("%d Invalid command.\n", ERROR+ILLEGAL_VALUE);
1367 * Initialize the SMTP outbound queue
1369 void smtp_init_spoolout(void) {
1370 struct quickroom qrbuf;
1373 * Create the room. This will silently fail if the room already
1374 * exists, and that's perfectly ok, because we want it to exist.
1376 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0);
1379 * Make sure it's set to be a "system room" so it doesn't show up
1380 * in the <K>nown rooms list for Aides.
1382 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1383 qrbuf.QRflags2 |= QR2_SYSTEM;
1391 /*****************************************************************************/
1392 /* MODULE INITIALIZATION STUFF */
1393 /*****************************************************************************/
1396 char *Dynamic_Module_Init(void)
1398 SYM_SMTP = CtdlGetDynamicSymbol();
1400 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1405 CtdlRegisterServiceHook(0, /* ...and locally */
1410 smtp_init_spoolout();
1411 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1412 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");