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>
34 #include <sys/socket.h>
35 #include <netinet/in.h>
36 #include <arpa/inet.h>
39 #include "sysdep_decls.h"
40 #include "citserver.h"
44 #include "serv_extensions.h"
51 #include "internet_addressing.h"
54 #include "clientsocket.h"
55 #include "locate_host.h"
62 struct citsmtp { /* Information about the current session */
65 struct usersupp vrfy_buffer;
70 int number_of_recipients;
73 int message_originated_locally;
76 enum { /* Command states for login authentication */
82 enum { /* Delivery modes */
87 #define SMTP ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
88 #define SMTP_RECPS ((char *)CtdlGetUserData(SYM_SMTP_RECPS))
89 #define SMTP_ROOMS ((char *)CtdlGetUserData(SYM_SMTP_ROOMS))
95 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
99 /*****************************************************************************/
100 /* SMTP SERVER (INBOUND) STUFF */
101 /*****************************************************************************/
107 * Here's where our SMTP session begins its happy day.
109 void smtp_greeting(void) {
111 strcpy(CC->cs_clientname, "SMTP session");
112 CC->internal_pgm = 1;
113 CC->cs_flags |= CS_STEALTH;
114 CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
115 CtdlAllocUserData(SYM_SMTP_RECPS, SIZ);
116 CtdlAllocUserData(SYM_SMTP_ROOMS, SIZ);
117 snprintf(SMTP_RECPS, SIZ, "%s", "");
118 snprintf(SMTP_ROOMS, SIZ, "%s", "");
120 cprintf("220 %s ESMTP Citadel/UX server ready.\r\n", config.c_fqdn);
125 * Implement HELO and EHLO commands.
127 void smtp_hello(char *argbuf, int is_esmtp) {
129 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
132 cprintf("250 Greetings and joyous salutations.\r\n");
135 cprintf("250-Greetings and joyous salutations.\r\n");
136 cprintf("250-HELP\r\n");
137 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
138 cprintf("250-PIPELINING\r\n");
139 cprintf("250-AUTH=LOGIN\r\n");
140 cprintf("250 ENHANCEDSTATUSCODES\r\n");
146 * Implement HELP command.
148 void smtp_help(void) {
149 cprintf("214-Commands accepted:\r\n");
150 cprintf("214- DATA\r\n");
151 cprintf("214- EHLO\r\n");
152 cprintf("214- EXPN\r\n");
153 cprintf("214- HELO\r\n");
154 cprintf("214- HELP\r\n");
155 cprintf("214- MAIL\r\n");
156 cprintf("214- NOOP\r\n");
157 cprintf("214- QUIT\r\n");
158 cprintf("214- RCPT\r\n");
159 cprintf("214- RSET\r\n");
160 cprintf("214- VRFY\r\n");
168 void smtp_get_user(char *argbuf) {
172 CtdlDecodeBase64(username, argbuf, SIZ);
173 lprintf(9, "Trying <%s>\n", username);
174 if (CtdlLoginExistingUser(username) == login_ok) {
175 CtdlEncodeBase64(buf, "Password:", 9);
176 cprintf("334 %s\r\n", buf);
177 SMTP->command_state = smtp_password;
180 cprintf("500 5.7.0 No such user.\r\n");
181 SMTP->command_state = smtp_command;
189 void smtp_get_pass(char *argbuf) {
192 CtdlDecodeBase64(password, argbuf, SIZ);
193 lprintf(9, "Trying <%s>\n", password);
194 if (CtdlTryPassword(password) == pass_ok) {
195 cprintf("235 2.0.0 Hello, %s\r\n", CC->usersupp.fullname);
196 lprintf(9, "SMTP authenticated login successful\n");
197 CC->internal_pgm = 0;
198 CC->cs_flags &= ~CS_STEALTH;
201 cprintf("500 5.7.0 Authentication failed.\r\n");
203 SMTP->command_state = smtp_command;
210 void smtp_auth(char *argbuf) {
213 if (strncasecmp(argbuf, "login", 5) ) {
214 cprintf("550 5.7.4 We only support LOGIN authentication.\r\n");
218 if (strlen(argbuf) >= 7) {
219 smtp_get_user(&argbuf[6]);
223 CtdlEncodeBase64(buf, "Username:", 9);
224 cprintf("334 %s\r\n", buf);
225 SMTP->command_state = smtp_user;
231 * Back end for smtp_vrfy() command
233 void smtp_vrfy_backend(struct usersupp *us, void *data) {
235 if (!fuzzy_match(us, SMTP->vrfy_match)) {
237 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
243 * Implements the VRFY (verify user name) command.
244 * Performs fuzzy match on full user names.
246 void smtp_vrfy(char *argbuf) {
247 SMTP->vrfy_count = 0;
248 strcpy(SMTP->vrfy_match, argbuf);
249 ForEachUser(smtp_vrfy_backend, NULL);
251 if (SMTP->vrfy_count < 1) {
252 cprintf("550 5.1.1 String does not match anything.\r\n");
254 else if (SMTP->vrfy_count == 1) {
255 cprintf("250 %s <cit%ld@%s>\r\n",
256 SMTP->vrfy_buffer.fullname,
257 SMTP->vrfy_buffer.usernum,
260 else if (SMTP->vrfy_count > 1) {
261 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
270 * Back end for smtp_expn() command
272 void smtp_expn_backend(struct usersupp *us, void *data) {
274 if (!fuzzy_match(us, SMTP->vrfy_match)) {
276 if (SMTP->vrfy_count >= 1) {
277 cprintf("250-%s <cit%ld@%s>\r\n",
278 SMTP->vrfy_buffer.fullname,
279 SMTP->vrfy_buffer.usernum,
284 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
290 * Implements the EXPN (expand user name) command.
291 * Performs fuzzy match on full user names.
293 void smtp_expn(char *argbuf) {
294 SMTP->vrfy_count = 0;
295 strcpy(SMTP->vrfy_match, argbuf);
296 ForEachUser(smtp_expn_backend, NULL);
298 if (SMTP->vrfy_count < 1) {
299 cprintf("550 5.1.1 String does not match anything.\r\n");
301 else if (SMTP->vrfy_count >= 1) {
302 cprintf("250 %s <cit%ld@%s>\r\n",
303 SMTP->vrfy_buffer.fullname,
304 SMTP->vrfy_buffer.usernum,
311 * Implements the RSET (reset state) command.
312 * Currently this just zeroes out the state buffer. If pointers to data
313 * allocated with mallok() are ever placed in the state buffer, we have to
314 * be sure to phree() them first!
316 void smtp_rset(void) {
317 memset(SMTP, 0, sizeof(struct citsmtp));
320 * It is somewhat ambiguous whether we want to log out when a RSET
321 * command is issued. Here's the code to do it. It is commented out
322 * because some clients (such as Pine) issue RSET commands before
323 * each message, but still expect to be logged in.
325 * if (CC->logged_in) {
330 cprintf("250 2.0.0 Zap!\r\n");
334 * Clear out the portions of the state buffer that need to be cleared out
335 * after the DATA command finishes.
337 void smtp_data_clear(void) {
338 strcpy(SMTP->from, "");
339 strcpy(SMTP->recipients, "");
340 SMTP->number_of_recipients = 0;
341 SMTP->delivery_mode = 0;
342 SMTP->message_originated_locally = 0;
348 * Implements the "MAIL From:" command
350 void smtp_mail(char *argbuf) {
355 if (strlen(SMTP->from) != 0) {
356 cprintf("503 5.1.0 Only one sender permitted\r\n");
360 if (strncasecmp(argbuf, "From:", 5)) {
361 cprintf("501 5.1.7 Syntax error\r\n");
365 strcpy(SMTP->from, &argbuf[5]);
367 stripallbut(SMTP->from, '<', '>');
369 if (strlen(SMTP->from) == 0) {
370 cprintf("501 5.1.7 Empty sender name is not permitted\r\n");
374 /* If this SMTP connection is from a logged-in user, force the 'from'
375 * to be the user's Internet e-mail address as Citadel knows it.
378 strcpy(SMTP->from, CC->cs_inet_email);
379 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
380 SMTP->message_originated_locally = 1;
384 /* Otherwise, make sure outsiders aren't trying to forge mail from
388 process_rfc822_addr(SMTP->from, user, node, name);
389 if (CtdlHostAlias(node) != hostalias_nomatch) {
391 "You must log in to send mail from %s\r\n",
393 strcpy(SMTP->from, "");
398 cprintf("250 2.0.0 Sender ok\r\n");
404 * Implements the "RCPT To:" command
406 void smtp_rcpt(char *argbuf) {
408 char message_to_spammer[SIZ];
409 struct recptypes *valid = NULL;
411 if (strlen(SMTP->from) == 0) {
412 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
416 if (strncasecmp(argbuf, "To:", 3)) {
417 cprintf("501 5.1.7 Syntax error\r\n");
421 strcpy(recp, &argbuf[3]);
423 stripallbut(recp, '<', '>');
425 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
426 cprintf("452 4.5.3 Too many recipients\r\n");
431 if ( (!CC->logged_in) && (!CC->is_local_socket) ) {
432 if (rbl_check(message_to_spammer)) {
433 cprintf("550 %s\r\n", message_to_spammer);
434 /* no need to phree(valid), it's not allocated yet */
439 valid = validate_recipients(recp);
440 if (valid->num_error > 0) {
441 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
446 if (valid->num_internet > 0) {
447 if (SMTP->message_originated_locally == 0) {
448 cprintf("551 5.7.1 Relaying denied <%s>\r\n", recp);
454 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
455 if (strlen(SMTP->recipients) > 0) {
456 strcat(SMTP->recipients, ",");
458 strcat(SMTP->recipients, recp);
459 SMTP->number_of_recipients += 1;
466 * Implements the DATA command
468 void smtp_data(void) {
470 struct CtdlMessage *msg;
473 struct recptypes *valid;
476 if (strlen(SMTP->from) == 0) {
477 cprintf("503 5.5.1 Need MAIL command first.\r\n");
481 if (SMTP->number_of_recipients < 1) {
482 cprintf("503 5.5.1 Need RCPT command first.\r\n");
486 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
488 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
492 it should be Received: from %s (real.name.dom [w.x.y.z])
494 if (body != NULL) snprintf(body, 4096,
495 "Received: from %s (%s)\n"
502 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
505 "Unable to save message: internal error.\r\n");
509 lprintf(9, "Converting message...\n");
510 msg = convert_internet_message(body);
512 /* If the user is locally authenticated, FORCE the From: header to
513 * show up as the real sender. Yes, this violates the RFC standard,
514 * but IT MAKES SENSE. If you prefer strict RFC adherence over
515 * common sense, you can disable this in the configuration.
517 * We also set the "message room name" ('O' field) to MAILROOM
518 * (which is Mail> on most systems) to prevent it from getting set
519 * to something ugly like "0000058008.Sent Items>" when the message
520 * is read with a Citadel client.
522 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
523 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
524 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
525 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
526 if (msg->cm_fields['F'] != NULL) phree(msg->cm_fields['F']);
527 if (msg->cm_fields['O'] != NULL) phree(msg->cm_fields['O']);
528 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
529 msg->cm_fields['N'] = strdoop(config.c_nodename);
530 msg->cm_fields['H'] = strdoop(config.c_humannode);
531 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
532 msg->cm_fields['O'] = strdoop(MAILROOM);
535 /* Submit the message into the Citadel system. */
536 valid = validate_recipients(SMTP->recipients);
538 /* If there are modules that want to scan this message before final
539 * submission (such as virus checkers or spam filters), call them now
540 * and give them an opportunity to reject the message.
542 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
544 if (scan_errors > 0) { /* We don't want this message! */
546 if (msg->cm_fields['0'] == NULL) {
547 msg->cm_fields['0'] = strdoop(
548 "5.7.1 Message rejected by filter");
551 cprintf("550 %s\r\n", msg->cm_fields['0']);
554 else { /* Ok, we'll accept this message. */
555 msgnum = CtdlSubmitMsg(msg, valid, "");
557 cprintf("250 2.0.0 Message accepted.\r\n");
560 cprintf("550 5.5.0 Internal delivery error\r\n");
564 CtdlFreeMessage(msg);
566 smtp_data_clear(); /* clear out the buffers now */
573 * Main command loop for SMTP sessions.
575 void smtp_command_loop(void) {
579 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
580 if (client_gets(cmdbuf) < 1) {
581 lprintf(3, "SMTP socket is broken. Ending session.\n");
585 lprintf(5, "SMTP: %s\n", cmdbuf);
586 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
588 lprintf(9, "CC->logged_in = %d\n", CC->logged_in);
590 if (SMTP->command_state == smtp_user) {
591 smtp_get_user(cmdbuf);
594 else if (SMTP->command_state == smtp_password) {
595 smtp_get_pass(cmdbuf);
598 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
599 smtp_auth(&cmdbuf[5]);
602 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
606 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
607 smtp_hello(&cmdbuf[5], 1);
610 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
611 smtp_expn(&cmdbuf[5]);
614 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
615 smtp_hello(&cmdbuf[5], 0);
618 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
622 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
623 smtp_mail(&cmdbuf[5]);
626 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
627 cprintf("250 NOOP\r\n");
630 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
631 cprintf("221 Goodbye...\r\n");
636 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
637 smtp_rcpt(&cmdbuf[5]);
640 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
644 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
645 smtp_vrfy(&cmdbuf[5]);
649 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
657 /*****************************************************************************/
658 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
659 /*****************************************************************************/
666 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
669 void smtp_try(const char *key, const char *addr, int *status,
670 char *dsn, size_t n, long msgnum)
677 char user[SIZ], node[SIZ], name[SIZ];
683 size_t blocksize = 0;
686 /* Parse out the host portion of the recipient address */
687 process_rfc822_addr(addr, user, node, name);
689 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
692 /* Load the message out of the database into a temp file */
694 if (msg_fp == NULL) {
696 snprintf(dsn, n, "Error creating temporary file");
700 CtdlRedirectOutput(msg_fp, -1);
701 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
702 CtdlRedirectOutput(NULL, -1);
703 fseek(msg_fp, 0L, SEEK_END);
704 msg_size = ftell(msg_fp);
708 /* Extract something to send later in the 'MAIL From:' command */
709 strcpy(mailfrom, "");
713 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
714 if (!strncasecmp(buf, "From:", 5)) {
715 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
717 for (i=0; i<strlen(mailfrom); ++i) {
718 if (!isprint(mailfrom[i])) {
719 strcpy(&mailfrom[i], &mailfrom[i+1]);
724 /* Strip out parenthesized names */
727 for (i=0; i<strlen(mailfrom); ++i) {
728 if (mailfrom[i] == '(') lp = i;
729 if (mailfrom[i] == ')') rp = i;
731 if ((lp>0)&&(rp>lp)) {
732 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
735 /* Prefer brokketized names */
738 for (i=0; i<strlen(mailfrom); ++i) {
739 if (mailfrom[i] == '<') lp = i;
740 if (mailfrom[i] == '>') rp = i;
742 if ( (lp>=0) && (rp>lp) ) {
744 strcpy(mailfrom, &mailfrom[lp]);
749 } while (scan_done == 0);
750 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
752 /* Figure out what mail exchanger host we have to connect to */
753 num_mxhosts = getmx(mxhosts, node);
754 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
755 if (num_mxhosts < 1) {
757 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
761 for (mx=0; mx<num_mxhosts; ++mx) {
762 extract(buf, mxhosts, mx);
763 lprintf(9, "Trying <%s>\n", buf);
764 sock = sock_connect(buf, "25", "tcp");
765 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
766 if (sock >= 0) lprintf(9, "Connected!\n");
767 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
771 *status = 4; /* dsn is already filled in */
775 /* Process the SMTP greeting from the server */
776 if (ml_sock_gets(sock, buf) < 0) {
778 strcpy(dsn, "Connection broken during SMTP conversation");
781 lprintf(9, "<%s\n", buf);
785 safestrncpy(dsn, &buf[4], 1023);
790 safestrncpy(dsn, &buf[4], 1023);
795 /* At this point we know we are talking to a real SMTP server */
797 /* Do a HELO command */
798 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
799 lprintf(9, ">%s", buf);
800 sock_write(sock, buf, strlen(buf));
801 if (ml_sock_gets(sock, buf) < 0) {
803 strcpy(dsn, "Connection broken during SMTP HELO");
806 lprintf(9, "<%s\n", buf);
810 safestrncpy(dsn, &buf[4], 1023);
815 safestrncpy(dsn, &buf[4], 1023);
821 /* HELO succeeded, now try the MAIL From: command */
822 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
823 lprintf(9, ">%s", buf);
824 sock_write(sock, buf, strlen(buf));
825 if (ml_sock_gets(sock, buf) < 0) {
827 strcpy(dsn, "Connection broken during SMTP MAIL");
830 lprintf(9, "<%s\n", buf);
834 safestrncpy(dsn, &buf[4], 1023);
839 safestrncpy(dsn, &buf[4], 1023);
845 /* MAIL succeeded, now try the RCPT To: command */
846 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
847 lprintf(9, ">%s", buf);
848 sock_write(sock, buf, strlen(buf));
849 if (ml_sock_gets(sock, buf) < 0) {
851 strcpy(dsn, "Connection broken during SMTP RCPT");
854 lprintf(9, "<%s\n", buf);
858 safestrncpy(dsn, &buf[4], 1023);
863 safestrncpy(dsn, &buf[4], 1023);
869 /* RCPT succeeded, now try the DATA command */
870 lprintf(9, ">DATA\n");
871 sock_write(sock, "DATA\r\n", 6);
872 if (ml_sock_gets(sock, buf) < 0) {
874 strcpy(dsn, "Connection broken during SMTP DATA");
877 lprintf(9, "<%s\n", buf);
881 safestrncpy(dsn, &buf[4], 1023);
886 safestrncpy(dsn, &buf[4], 1023);
891 /* If we reach this point, the server is expecting data */
893 while (msg_size > 0) {
894 blocksize = sizeof(buf);
895 if (blocksize > msg_size) blocksize = msg_size;
896 fread(buf, blocksize, 1, msg_fp);
897 sock_write(sock, buf, blocksize);
898 msg_size -= blocksize;
900 if (buf[blocksize-1] != 10) {
901 lprintf(5, "Possible problem: message did not correctly "
902 "terminate. (expecting 0x10, got 0x%02x)\n",
906 sock_write(sock, ".\r\n", 3);
907 if (ml_sock_gets(sock, buf) < 0) {
909 strcpy(dsn, "Connection broken during SMTP message transmit");
912 lprintf(9, "%s\n", buf);
916 safestrncpy(dsn, &buf[4], 1023);
921 safestrncpy(dsn, &buf[4], 1023);
927 safestrncpy(dsn, &buf[4], 1023);
930 lprintf(9, ">QUIT\n");
931 sock_write(sock, "QUIT\r\n", 6);
932 ml_sock_gets(sock, buf);
933 lprintf(9, "<%s\n", buf);
935 bail: if (msg_fp != NULL) fclose(msg_fp);
943 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
944 * instructions for "5" codes (permanent fatal errors) and produce/deliver
945 * a "bounce" message (delivery status notification).
947 void smtp_do_bounce(char *instr) {
958 long bounce_msgid = (-1);
959 time_t submitted = 0L;
960 struct CtdlMessage *bmsg = NULL;
962 struct recptypes *valid;
963 int successful_bounce = 0;
965 lprintf(9, "smtp_do_bounce() called\n");
966 strcpy(bounceto, "");
968 lines = num_tokens(instr, '\n');
971 /* See if it's time to give up on delivery of this message */
972 for (i=0; i<lines; ++i) {
973 extract_token(buf, instr, i, '\n');
974 extract(key, buf, 0);
975 extract(addr, buf, 1);
976 if (!strcasecmp(key, "submitted")) {
977 submitted = atol(addr);
981 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
987 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
988 if (bmsg == NULL) return;
989 memset(bmsg, 0, sizeof(struct CtdlMessage));
991 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
992 bmsg->cm_anon_type = MES_NORMAL;
993 bmsg->cm_format_type = 1;
994 bmsg->cm_fields['A'] = strdoop("Citadel");
995 bmsg->cm_fields['O'] = strdoop(MAILROOM);
996 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
998 if (give_up) bmsg->cm_fields['M'] = strdoop(
999 "A message you sent could not be delivered to some or all of its recipients\n"
1000 "due to prolonged unavailability of its destination(s).\n"
1001 "Giving up on the following addresses:\n\n"
1004 else bmsg->cm_fields['M'] = strdoop(
1005 "A message you sent could not be delivered to some or all of its recipients.\n"
1006 "The following addresses were undeliverable:\n\n"
1010 * Now go through the instructions checking for stuff.
1012 for (i=0; i<lines; ++i) {
1013 extract_token(buf, instr, i, '\n');
1014 extract(key, buf, 0);
1015 extract(addr, buf, 1);
1016 status = extract_int(buf, 2);
1017 extract(dsn, buf, 3);
1020 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1021 key, addr, status, dsn);
1023 if (!strcasecmp(key, "bounceto")) {
1024 strcpy(bounceto, addr);
1028 (!strcasecmp(key, "local"))
1029 || (!strcasecmp(key, "remote"))
1030 || (!strcasecmp(key, "ignet"))
1031 || (!strcasecmp(key, "room"))
1033 if (status == 5) bounce_this = 1;
1034 if (give_up) bounce_this = 1;
1040 if (bmsg->cm_fields['M'] == NULL) {
1041 lprintf(2, "ERROR ... M field is null "
1042 "(%s:%d)\n", __FILE__, __LINE__);
1045 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1046 strlen(bmsg->cm_fields['M']) + 1024 );
1047 strcat(bmsg->cm_fields['M'], addr);
1048 strcat(bmsg->cm_fields['M'], ": ");
1049 strcat(bmsg->cm_fields['M'], dsn);
1050 strcat(bmsg->cm_fields['M'], "\n");
1052 remove_token(instr, i, '\n');
1058 /* Deliver the bounce if there's anything worth mentioning */
1059 lprintf(9, "num_bounces = %d\n", num_bounces);
1060 if (num_bounces > 0) {
1062 /* First try the user who sent the message */
1063 lprintf(9, "bounce to user? <%s>\n", bounceto);
1064 if (strlen(bounceto) == 0) {
1065 lprintf(7, "No bounce address specified\n");
1066 bounce_msgid = (-1L);
1069 /* Can we deliver the bounce to the original sender? */
1070 valid = validate_recipients(bounceto);
1071 if (valid != NULL) {
1072 if (valid->num_error == 0) {
1073 CtdlSubmitMsg(bmsg, valid, "");
1074 successful_bounce = 1;
1078 /* If not, post it in the Aide> room */
1079 if (successful_bounce == 0) {
1080 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1083 /* Free up the memory we used */
1084 if (valid != NULL) {
1089 CtdlFreeMessage(bmsg);
1090 lprintf(9, "Done processing bounces\n");
1095 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1096 * set of delivery instructions for completed deliveries and remove them.
1098 * It returns the number of incomplete deliveries remaining.
1100 int smtp_purge_completed_deliveries(char *instr) {
1111 lines = num_tokens(instr, '\n');
1112 for (i=0; i<lines; ++i) {
1113 extract_token(buf, instr, i, '\n');
1114 extract(key, buf, 0);
1115 extract(addr, buf, 1);
1116 status = extract_int(buf, 2);
1117 extract(dsn, buf, 3);
1122 (!strcasecmp(key, "local"))
1123 || (!strcasecmp(key, "remote"))
1124 || (!strcasecmp(key, "ignet"))
1125 || (!strcasecmp(key, "room"))
1127 if (status == 2) completed = 1;
1132 remove_token(instr, i, '\n');
1145 * Called by smtp_do_queue() to handle an individual message.
1147 void smtp_do_procmsg(long msgnum, void *userdata) {
1148 struct CtdlMessage *msg;
1150 char *results = NULL;
1158 long text_msgid = (-1);
1159 int incomplete_deliveries_remaining;
1160 time_t attempted = 0L;
1161 time_t last_attempted = 0L;
1162 time_t retry = SMTP_RETRY_INTERVAL;
1164 lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1166 msg = CtdlFetchMessage(msgnum);
1168 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1172 instr = strdoop(msg->cm_fields['M']);
1173 CtdlFreeMessage(msg);
1175 /* Strip out the headers amd any other non-instruction line */
1176 lines = num_tokens(instr, '\n');
1177 for (i=0; i<lines; ++i) {
1178 extract_token(buf, instr, i, '\n');
1179 if (num_tokens(buf, '|') < 2) {
1180 remove_token(instr, i, '\n');
1186 /* Learn the message ID and find out about recent delivery attempts */
1187 lines = num_tokens(instr, '\n');
1188 for (i=0; i<lines; ++i) {
1189 extract_token(buf, instr, i, '\n');
1190 extract(key, buf, 0);
1191 if (!strcasecmp(key, "msgid")) {
1192 text_msgid = extract_long(buf, 1);
1194 if (!strcasecmp(key, "retry")) {
1195 /* double the retry interval after each attempt */
1196 retry = extract_long(buf, 1) * 2L;
1197 if (retry > SMTP_RETRY_MAX) {
1198 retry = SMTP_RETRY_MAX;
1200 remove_token(instr, i, '\n');
1202 if (!strcasecmp(key, "attempted")) {
1203 attempted = extract_long(buf, 1);
1204 if (attempted > last_attempted)
1205 last_attempted = attempted;
1210 * Postpone delivery if we've already tried recently.
1212 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1213 lprintf(7, "Retry time not yet reached.\n");
1220 * Bail out if there's no actual message associated with this
1222 if (text_msgid < 0L) {
1223 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1228 /* Plow through the instructions looking for 'remote' directives and
1229 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1230 * were experienced and it's time to try again)
1232 lines = num_tokens(instr, '\n');
1233 for (i=0; i<lines; ++i) {
1234 extract_token(buf, instr, i, '\n');
1235 extract(key, buf, 0);
1236 extract(addr, buf, 1);
1237 status = extract_int(buf, 2);
1238 extract(dsn, buf, 3);
1239 if ( (!strcasecmp(key, "remote"))
1240 && ((status==0)||(status==3)||(status==4)) ) {
1242 /* Remove this "remote" instruction from the set,
1243 * but replace the set's final newline if
1244 * remove_token() stripped it. It has to be there.
1246 remove_token(instr, i, '\n');
1247 if (instr[strlen(instr)-1] != '\n') {
1248 strcat(instr, "\n");
1253 lprintf(9, "SMTP: Trying <%s>\n", addr);
1254 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1256 if (results == NULL) {
1257 results = mallok(1024);
1258 memset(results, 0, 1024);
1261 results = reallok(results,
1262 strlen(results) + 1024);
1264 snprintf(&results[strlen(results)], 1024,
1266 key, addr, status, dsn);
1271 if (results != NULL) {
1272 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1273 strcat(instr, results);
1278 /* Generate 'bounce' messages */
1279 smtp_do_bounce(instr);
1281 /* Go through the delivery list, deleting completed deliveries */
1282 incomplete_deliveries_remaining =
1283 smtp_purge_completed_deliveries(instr);
1287 * No delivery instructions remain, so delete both the instructions
1288 * message and the message message.
1290 if (incomplete_deliveries_remaining <= 0) {
1291 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1292 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1297 * Uncompleted delivery instructions remain, so delete the old
1298 * instructions and replace with the updated ones.
1300 if (incomplete_deliveries_remaining > 0) {
1301 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1302 msg = mallok(sizeof(struct CtdlMessage));
1303 memset(msg, 0, sizeof(struct CtdlMessage));
1304 msg->cm_magic = CTDLMESSAGE_MAGIC;
1305 msg->cm_anon_type = MES_NORMAL;
1306 msg->cm_format_type = FMT_RFC822;
1307 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1308 snprintf(msg->cm_fields['M'],
1310 "Content-type: %s\n\n%s\n"
1313 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1315 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1316 CtdlFreeMessage(msg);
1326 * Run through the queue sending out messages.
1328 void smtp_do_queue(void) {
1329 static int doing_queue = 0;
1332 * This is a simple concurrency check to make sure only one queue run
1333 * is done at a time. We could do this with a mutex, but since we
1334 * don't really require extremely fine granularity here, we'll do it
1335 * with a static variable instead.
1337 if (doing_queue) return;
1341 * Go ahead and run the queue
1343 lprintf(7, "SMTP: processing outbound queue\n");
1345 if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1346 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1349 CtdlForEachMessage(MSGS_ALL, 0L,
1350 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1352 lprintf(7, "SMTP: queue run completed\n");
1359 /*****************************************************************************/
1360 /* SMTP UTILITY COMMANDS */
1361 /*****************************************************************************/
1363 void cmd_smtp(char *argbuf) {
1370 if (CtdlAccessCheck(ac_aide)) return;
1372 extract(cmd, argbuf, 0);
1374 if (!strcasecmp(cmd, "mx")) {
1375 extract(node, argbuf, 1);
1376 num_mxhosts = getmx(buf, node);
1377 cprintf("%d %d MX hosts listed for %s\n",
1378 LISTING_FOLLOWS, num_mxhosts, node);
1379 for (i=0; i<num_mxhosts; ++i) {
1380 extract(node, buf, i);
1381 cprintf("%s\n", node);
1387 else if (!strcasecmp(cmd, "runqueue")) {
1389 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1394 cprintf("%d Invalid command.\n", ERROR+ILLEGAL_VALUE);
1401 * Initialize the SMTP outbound queue
1403 void smtp_init_spoolout(void) {
1404 struct quickroom qrbuf;
1407 * Create the room. This will silently fail if the room already
1408 * exists, and that's perfectly ok, because we want it to exist.
1410 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0);
1413 * Make sure it's set to be a "system room" so it doesn't show up
1414 * in the <K>nown rooms list for Aides.
1416 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1417 qrbuf.QRflags2 |= QR2_SYSTEM;
1425 /*****************************************************************************/
1426 /* MODULE INITIALIZATION STUFF */
1427 /*****************************************************************************/
1430 char *serv_smtp_init(void)
1432 SYM_SMTP = CtdlGetDynamicSymbol();
1434 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1439 CtdlRegisterServiceHook(0, /* ...and locally */
1444 smtp_init_spoolout();
1445 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1446 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");