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 <%s> - relaying denied\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);
762 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
763 extract(buf, mxhosts, mx);
764 lprintf(9, "Trying <%s>\n", buf);
765 sock = sock_connect(buf, "25", "tcp");
766 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
767 if (sock >= 0) lprintf(9, "Connected!\n");
768 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
772 *status = 4; /* dsn is already filled in */
776 /* Process the SMTP greeting from the server */
777 if (ml_sock_gets(sock, buf) < 0) {
779 strcpy(dsn, "Connection broken during SMTP conversation");
782 lprintf(9, "<%s\n", buf);
786 safestrncpy(dsn, &buf[4], 1023);
791 safestrncpy(dsn, &buf[4], 1023);
796 /* At this point we know we are talking to a real SMTP server */
798 /* Do a HELO command */
799 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
800 lprintf(9, ">%s", buf);
801 sock_write(sock, buf, strlen(buf));
802 if (ml_sock_gets(sock, buf) < 0) {
804 strcpy(dsn, "Connection broken during SMTP HELO");
807 lprintf(9, "<%s\n", buf);
811 safestrncpy(dsn, &buf[4], 1023);
816 safestrncpy(dsn, &buf[4], 1023);
822 /* HELO succeeded, now try the MAIL From: command */
823 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
824 lprintf(9, ">%s", buf);
825 sock_write(sock, buf, strlen(buf));
826 if (ml_sock_gets(sock, buf) < 0) {
828 strcpy(dsn, "Connection broken during SMTP MAIL");
831 lprintf(9, "<%s\n", buf);
835 safestrncpy(dsn, &buf[4], 1023);
840 safestrncpy(dsn, &buf[4], 1023);
846 /* MAIL succeeded, now try the RCPT To: command */
847 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
848 lprintf(9, ">%s", buf);
849 sock_write(sock, buf, strlen(buf));
850 if (ml_sock_gets(sock, buf) < 0) {
852 strcpy(dsn, "Connection broken during SMTP RCPT");
855 lprintf(9, "<%s\n", buf);
859 safestrncpy(dsn, &buf[4], 1023);
864 safestrncpy(dsn, &buf[4], 1023);
870 /* RCPT succeeded, now try the DATA command */
871 lprintf(9, ">DATA\n");
872 sock_write(sock, "DATA\r\n", 6);
873 if (ml_sock_gets(sock, buf) < 0) {
875 strcpy(dsn, "Connection broken during SMTP DATA");
878 lprintf(9, "<%s\n", buf);
882 safestrncpy(dsn, &buf[4], 1023);
887 safestrncpy(dsn, &buf[4], 1023);
892 /* If we reach this point, the server is expecting data */
894 while (msg_size > 0) {
895 blocksize = sizeof(buf);
896 if (blocksize > msg_size) blocksize = msg_size;
897 fread(buf, blocksize, 1, msg_fp);
898 sock_write(sock, buf, blocksize);
899 msg_size -= blocksize;
901 if (buf[blocksize-1] != 10) {
902 lprintf(5, "Possible problem: message did not correctly "
903 "terminate. (expecting 0x10, got 0x%02x)\n",
907 sock_write(sock, ".\r\n", 3);
908 if (ml_sock_gets(sock, buf) < 0) {
910 strcpy(dsn, "Connection broken during SMTP message transmit");
913 lprintf(9, "%s\n", buf);
917 safestrncpy(dsn, &buf[4], 1023);
922 safestrncpy(dsn, &buf[4], 1023);
928 safestrncpy(dsn, &buf[4], 1023);
931 lprintf(9, ">QUIT\n");
932 sock_write(sock, "QUIT\r\n", 6);
933 ml_sock_gets(sock, buf);
934 lprintf(9, "<%s\n", buf);
936 bail: if (msg_fp != NULL) fclose(msg_fp);
944 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
945 * instructions for "5" codes (permanent fatal errors) and produce/deliver
946 * a "bounce" message (delivery status notification).
948 void smtp_do_bounce(char *instr) {
959 long bounce_msgid = (-1);
960 time_t submitted = 0L;
961 struct CtdlMessage *bmsg = NULL;
963 struct recptypes *valid;
964 int successful_bounce = 0;
966 lprintf(9, "smtp_do_bounce() called\n");
967 strcpy(bounceto, "");
969 lines = num_tokens(instr, '\n');
972 /* See if it's time to give up on delivery of this message */
973 for (i=0; i<lines; ++i) {
974 extract_token(buf, instr, i, '\n');
975 extract(key, buf, 0);
976 extract(addr, buf, 1);
977 if (!strcasecmp(key, "submitted")) {
978 submitted = atol(addr);
982 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
988 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
989 if (bmsg == NULL) return;
990 memset(bmsg, 0, sizeof(struct CtdlMessage));
992 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
993 bmsg->cm_anon_type = MES_NORMAL;
994 bmsg->cm_format_type = 1;
995 bmsg->cm_fields['A'] = strdoop("Citadel");
996 bmsg->cm_fields['O'] = strdoop(MAILROOM);
997 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
999 if (give_up) bmsg->cm_fields['M'] = strdoop(
1000 "A message you sent could not be delivered to some or all of its recipients\n"
1001 "due to prolonged unavailability of its destination(s).\n"
1002 "Giving up on the following addresses:\n\n"
1005 else bmsg->cm_fields['M'] = strdoop(
1006 "A message you sent could not be delivered to some or all of its recipients.\n"
1007 "The following addresses were undeliverable:\n\n"
1011 * Now go through the instructions checking for stuff.
1013 for (i=0; i<lines; ++i) {
1014 extract_token(buf, instr, i, '\n');
1015 extract(key, buf, 0);
1016 extract(addr, buf, 1);
1017 status = extract_int(buf, 2);
1018 extract(dsn, buf, 3);
1021 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1022 key, addr, status, dsn);
1024 if (!strcasecmp(key, "bounceto")) {
1025 strcpy(bounceto, addr);
1029 (!strcasecmp(key, "local"))
1030 || (!strcasecmp(key, "remote"))
1031 || (!strcasecmp(key, "ignet"))
1032 || (!strcasecmp(key, "room"))
1034 if (status == 5) bounce_this = 1;
1035 if (give_up) bounce_this = 1;
1041 if (bmsg->cm_fields['M'] == NULL) {
1042 lprintf(2, "ERROR ... M field is null "
1043 "(%s:%d)\n", __FILE__, __LINE__);
1046 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1047 strlen(bmsg->cm_fields['M']) + 1024 );
1048 strcat(bmsg->cm_fields['M'], addr);
1049 strcat(bmsg->cm_fields['M'], ": ");
1050 strcat(bmsg->cm_fields['M'], dsn);
1051 strcat(bmsg->cm_fields['M'], "\n");
1053 remove_token(instr, i, '\n');
1059 /* Deliver the bounce if there's anything worth mentioning */
1060 lprintf(9, "num_bounces = %d\n", num_bounces);
1061 if (num_bounces > 0) {
1063 /* First try the user who sent the message */
1064 lprintf(9, "bounce to user? <%s>\n", bounceto);
1065 if (strlen(bounceto) == 0) {
1066 lprintf(7, "No bounce address specified\n");
1067 bounce_msgid = (-1L);
1070 /* Can we deliver the bounce to the original sender? */
1071 valid = validate_recipients(bounceto);
1072 if (valid != NULL) {
1073 if (valid->num_error == 0) {
1074 CtdlSubmitMsg(bmsg, valid, "");
1075 successful_bounce = 1;
1079 /* If not, post it in the Aide> room */
1080 if (successful_bounce == 0) {
1081 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1084 /* Free up the memory we used */
1085 if (valid != NULL) {
1090 CtdlFreeMessage(bmsg);
1091 lprintf(9, "Done processing bounces\n");
1096 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1097 * set of delivery instructions for completed deliveries and remove them.
1099 * It returns the number of incomplete deliveries remaining.
1101 int smtp_purge_completed_deliveries(char *instr) {
1112 lines = num_tokens(instr, '\n');
1113 for (i=0; i<lines; ++i) {
1114 extract_token(buf, instr, i, '\n');
1115 extract(key, buf, 0);
1116 extract(addr, buf, 1);
1117 status = extract_int(buf, 2);
1118 extract(dsn, buf, 3);
1123 (!strcasecmp(key, "local"))
1124 || (!strcasecmp(key, "remote"))
1125 || (!strcasecmp(key, "ignet"))
1126 || (!strcasecmp(key, "room"))
1128 if (status == 2) completed = 1;
1133 remove_token(instr, i, '\n');
1146 * Called by smtp_do_queue() to handle an individual message.
1148 void smtp_do_procmsg(long msgnum, void *userdata) {
1149 struct CtdlMessage *msg;
1151 char *results = NULL;
1159 long text_msgid = (-1);
1160 int incomplete_deliveries_remaining;
1161 time_t attempted = 0L;
1162 time_t last_attempted = 0L;
1163 time_t retry = SMTP_RETRY_INTERVAL;
1165 lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1167 msg = CtdlFetchMessage(msgnum);
1169 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1173 instr = strdoop(msg->cm_fields['M']);
1174 CtdlFreeMessage(msg);
1176 /* Strip out the headers amd any other non-instruction line */
1177 lines = num_tokens(instr, '\n');
1178 for (i=0; i<lines; ++i) {
1179 extract_token(buf, instr, i, '\n');
1180 if (num_tokens(buf, '|') < 2) {
1181 remove_token(instr, i, '\n');
1187 /* Learn the message ID and find out about recent delivery attempts */
1188 lines = num_tokens(instr, '\n');
1189 for (i=0; i<lines; ++i) {
1190 extract_token(buf, instr, i, '\n');
1191 extract(key, buf, 0);
1192 if (!strcasecmp(key, "msgid")) {
1193 text_msgid = extract_long(buf, 1);
1195 if (!strcasecmp(key, "retry")) {
1196 /* double the retry interval after each attempt */
1197 retry = extract_long(buf, 1) * 2L;
1198 if (retry > SMTP_RETRY_MAX) {
1199 retry = SMTP_RETRY_MAX;
1201 remove_token(instr, i, '\n');
1203 if (!strcasecmp(key, "attempted")) {
1204 attempted = extract_long(buf, 1);
1205 if (attempted > last_attempted)
1206 last_attempted = attempted;
1211 * Postpone delivery if we've already tried recently.
1213 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1214 lprintf(7, "Retry time not yet reached.\n");
1221 * Bail out if there's no actual message associated with this
1223 if (text_msgid < 0L) {
1224 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1229 /* Plow through the instructions looking for 'remote' directives and
1230 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1231 * were experienced and it's time to try again)
1233 lines = num_tokens(instr, '\n');
1234 for (i=0; i<lines; ++i) {
1235 extract_token(buf, instr, i, '\n');
1236 extract(key, buf, 0);
1237 extract(addr, buf, 1);
1238 status = extract_int(buf, 2);
1239 extract(dsn, buf, 3);
1240 if ( (!strcasecmp(key, "remote"))
1241 && ((status==0)||(status==3)||(status==4)) ) {
1243 /* Remove this "remote" instruction from the set,
1244 * but replace the set's final newline if
1245 * remove_token() stripped it. It has to be there.
1247 remove_token(instr, i, '\n');
1248 if (instr[strlen(instr)-1] != '\n') {
1249 strcat(instr, "\n");
1254 lprintf(9, "SMTP: Trying <%s>\n", addr);
1255 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1257 if (results == NULL) {
1258 results = mallok(1024);
1259 memset(results, 0, 1024);
1262 results = reallok(results,
1263 strlen(results) + 1024);
1265 snprintf(&results[strlen(results)], 1024,
1267 key, addr, status, dsn);
1272 if (results != NULL) {
1273 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1274 strcat(instr, results);
1279 /* Generate 'bounce' messages */
1280 smtp_do_bounce(instr);
1282 /* Go through the delivery list, deleting completed deliveries */
1283 incomplete_deliveries_remaining =
1284 smtp_purge_completed_deliveries(instr);
1288 * No delivery instructions remain, so delete both the instructions
1289 * message and the message message.
1291 if (incomplete_deliveries_remaining <= 0) {
1292 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1293 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1298 * Uncompleted delivery instructions remain, so delete the old
1299 * instructions and replace with the updated ones.
1301 if (incomplete_deliveries_remaining > 0) {
1302 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1303 msg = mallok(sizeof(struct CtdlMessage));
1304 memset(msg, 0, sizeof(struct CtdlMessage));
1305 msg->cm_magic = CTDLMESSAGE_MAGIC;
1306 msg->cm_anon_type = MES_NORMAL;
1307 msg->cm_format_type = FMT_RFC822;
1308 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1309 snprintf(msg->cm_fields['M'],
1311 "Content-type: %s\n\n%s\n"
1314 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1316 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1317 CtdlFreeMessage(msg);
1327 * Run through the queue sending out messages.
1329 void smtp_do_queue(void) {
1330 static int doing_queue = 0;
1333 * This is a simple concurrency check to make sure only one queue run
1334 * is done at a time. We could do this with a mutex, but since we
1335 * don't really require extremely fine granularity here, we'll do it
1336 * with a static variable instead.
1338 if (doing_queue) return;
1342 * Go ahead and run the queue
1344 lprintf(7, "SMTP: processing outbound queue\n");
1346 if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1347 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1350 CtdlForEachMessage(MSGS_ALL, 0L,
1351 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1353 lprintf(7, "SMTP: queue run completed\n");
1360 /*****************************************************************************/
1361 /* SMTP UTILITY COMMANDS */
1362 /*****************************************************************************/
1364 void cmd_smtp(char *argbuf) {
1371 if (CtdlAccessCheck(ac_aide)) return;
1373 extract(cmd, argbuf, 0);
1375 if (!strcasecmp(cmd, "mx")) {
1376 extract(node, argbuf, 1);
1377 num_mxhosts = getmx(buf, node);
1378 cprintf("%d %d MX hosts listed for %s\n",
1379 LISTING_FOLLOWS, num_mxhosts, node);
1380 for (i=0; i<num_mxhosts; ++i) {
1381 extract(node, buf, i);
1382 cprintf("%s\n", node);
1388 else if (!strcasecmp(cmd, "runqueue")) {
1390 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1395 cprintf("%d Invalid command.\n", ERROR+ILLEGAL_VALUE);
1402 * Initialize the SMTP outbound queue
1404 void smtp_init_spoolout(void) {
1405 struct quickroom qrbuf;
1408 * Create the room. This will silently fail if the room already
1409 * exists, and that's perfectly ok, because we want it to exist.
1411 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0);
1414 * Make sure it's set to be a "system room" so it doesn't show up
1415 * in the <K>nown rooms list for Aides.
1417 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1418 qrbuf.QRflags2 |= QR2_SYSTEM;
1426 /*****************************************************************************/
1427 /* MODULE INITIALIZATION STUFF */
1428 /*****************************************************************************/
1431 char *serv_smtp_init(void)
1433 SYM_SMTP = CtdlGetDynamicSymbol();
1435 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1440 CtdlRegisterServiceHook(0, /* ...and locally */
1445 smtp_init_spoolout();
1446 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1447 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");