4 * This module is an SMTP and ESMTP implementation for the Citadel system.
5 * It is compliant with all of the following:
7 * RFC 821 - Simple Mail Transfer Protocol
8 * RFC 876 - Survey of SMTP Implementations
9 * RFC 1047 - Duplicate messages and SMTP
10 * RFC 1854 - command pipelining
11 * RFC 1869 - Extended Simple Mail Transfer Protocol
12 * RFC 1870 - SMTP Service Extension for Message Size Declaration
13 * RFC 1893 - Enhanced Mail System Status Codes
14 * RFC 2033 - Local Mail Transfer Protocol
15 * RFC 2034 - SMTP Service Extension for Returning Enhanced Error Codes
16 * RFC 2197 - SMTP Service Extension for Command Pipelining
17 * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
18 * RFC 2554 - SMTP Service Extension for Authentication
19 * RFC 2821 - Simple Mail Transfer Protocol
20 * RFC 2822 - Internet Message Format
21 * RFC 2920 - SMTP Service Extension for Command Pipelining
33 #include <sys/types.h>
35 #if TIME_WITH_SYS_TIME
36 # include <sys/time.h>
40 # include <sys/time.h>
50 #include <sys/socket.h>
51 #include <netinet/in.h>
52 #include <arpa/inet.h>
55 #include "sysdep_decls.h"
56 #include "citserver.h"
60 #include "serv_extensions.h"
67 #include "internet_addressing.h"
70 #include "clientsocket.h"
71 #include "locate_host.h"
74 #include "serv_crypto.h"
83 struct citsmtp { /* Information about the current session */
86 struct ctdluser vrfy_buffer;
91 int number_of_recipients;
93 int message_originated_locally;
97 enum { /* Command states for login authentication */
103 enum { /* Delivery modes */
108 #define SMTP ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
109 #define SMTP_RECPS ((char *)CtdlGetUserData(SYM_SMTP_RECPS))
110 #define SMTP_ROOMS ((char *)CtdlGetUserData(SYM_SMTP_ROOMS))
113 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
117 /*****************************************************************************/
118 /* SMTP SERVER (INBOUND) STUFF */
119 /*****************************************************************************/
125 * Here's where our SMTP session begins its happy day.
127 void smtp_greeting(void) {
129 strcpy(CC->cs_clientname, "SMTP session");
130 CC->internal_pgm = 1;
131 CC->cs_flags |= CS_STEALTH;
132 CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
133 CtdlAllocUserData(SYM_SMTP_RECPS, SIZ);
134 CtdlAllocUserData(SYM_SMTP_ROOMS, SIZ);
135 snprintf(SMTP_RECPS, SIZ, "%s", "");
136 snprintf(SMTP_ROOMS, SIZ, "%s", "");
138 cprintf("220 %s ESMTP Citadel/UX server ready.\r\n", config.c_fqdn);
142 * LMTP is like SMTP but with some extra bonus footage added.
144 void lmtp_greeting(void) {
151 * Implement HELO and EHLO commands.
153 * which_command: 0=HELO, 1=EHLO, 2=LHLO
155 void smtp_hello(char *argbuf, int which_command) {
157 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
159 if ( (which_command != 2) && (SMTP->is_lmtp) ) {
160 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
164 if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
165 cprintf("500 LHLO is only allowed when running LMTP\r\n");
169 if (which_command == 0) {
170 cprintf("250 Hello %s (%s [%s])\r\n",
177 if (which_command == 1) {
178 cprintf("250-Hello %s (%s [%s])\r\n",
185 cprintf("250-Greetings and joyous salutations.\r\n");
187 cprintf("250-HELP\r\n");
188 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
189 cprintf("250-PIPELINING\r\n");
190 cprintf("250-AUTH=LOGIN\r\n");
192 cprintf("250-STARTTLS\r\n");
194 cprintf("250 ENHANCEDSTATUSCODES\r\n");
201 * Implement HELP command.
203 void smtp_help(void) {
204 cprintf("214-Commands accepted:\r\n");
205 cprintf("214- DATA\r\n");
206 cprintf("214- EHLO\r\n");
207 cprintf("214- EXPN\r\n");
208 cprintf("214- HELO\r\n");
209 cprintf("214- HELP\r\n");
210 cprintf("214- MAIL\r\n");
211 cprintf("214- NOOP\r\n");
212 cprintf("214- QUIT\r\n");
213 cprintf("214- RCPT\r\n");
214 cprintf("214- RSET\r\n");
215 cprintf("214- VRFY\r\n");
223 void smtp_get_user(char *argbuf) {
227 CtdlDecodeBase64(username, argbuf, SIZ);
228 lprintf(9, "Trying <%s>\n", username);
229 if (CtdlLoginExistingUser(username) == login_ok) {
230 CtdlEncodeBase64(buf, "Password:", 9);
231 cprintf("334 %s\r\n", buf);
232 SMTP->command_state = smtp_password;
235 cprintf("500 5.7.0 No such user.\r\n");
236 SMTP->command_state = smtp_command;
244 void smtp_get_pass(char *argbuf) {
247 CtdlDecodeBase64(password, argbuf, SIZ);
248 lprintf(9, "Trying <%s>\n", password);
249 if (CtdlTryPassword(password) == pass_ok) {
250 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
251 lprintf(9, "SMTP authenticated login successful\n");
252 CC->internal_pgm = 0;
253 CC->cs_flags &= ~CS_STEALTH;
256 cprintf("535 5.7.0 Authentication failed.\r\n");
258 SMTP->command_state = smtp_command;
265 void smtp_auth(char *argbuf) {
268 if (strncasecmp(argbuf, "login", 5) ) {
269 cprintf("504 5.7.4 We only support LOGIN authentication.\r\n");
273 if (strlen(argbuf) >= 7) {
274 smtp_get_user(&argbuf[6]);
278 CtdlEncodeBase64(buf, "Username:", 9);
279 cprintf("334 %s\r\n", buf);
280 SMTP->command_state = smtp_user;
286 * Back end for smtp_vrfy() command
288 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
290 if (!fuzzy_match(us, SMTP->vrfy_match)) {
292 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
298 * Implements the VRFY (verify user name) command.
299 * Performs fuzzy match on full user names.
301 void smtp_vrfy(char *argbuf) {
302 SMTP->vrfy_count = 0;
303 strcpy(SMTP->vrfy_match, argbuf);
304 ForEachUser(smtp_vrfy_backend, NULL);
306 if (SMTP->vrfy_count < 1) {
307 cprintf("550 5.1.1 String does not match anything.\r\n");
309 else if (SMTP->vrfy_count == 1) {
310 cprintf("250 %s <cit%ld@%s>\r\n",
311 SMTP->vrfy_buffer.fullname,
312 SMTP->vrfy_buffer.usernum,
315 else if (SMTP->vrfy_count > 1) {
316 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
325 * Back end for smtp_expn() command
327 void smtp_expn_backend(struct ctdluser *us, void *data) {
329 if (!fuzzy_match(us, SMTP->vrfy_match)) {
331 if (SMTP->vrfy_count >= 1) {
332 cprintf("250-%s <cit%ld@%s>\r\n",
333 SMTP->vrfy_buffer.fullname,
334 SMTP->vrfy_buffer.usernum,
339 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
345 * Implements the EXPN (expand user name) command.
346 * Performs fuzzy match on full user names.
348 void smtp_expn(char *argbuf) {
349 SMTP->vrfy_count = 0;
350 strcpy(SMTP->vrfy_match, argbuf);
351 ForEachUser(smtp_expn_backend, NULL);
353 if (SMTP->vrfy_count < 1) {
354 cprintf("550 5.1.1 String does not match anything.\r\n");
356 else if (SMTP->vrfy_count >= 1) {
357 cprintf("250 %s <cit%ld@%s>\r\n",
358 SMTP->vrfy_buffer.fullname,
359 SMTP->vrfy_buffer.usernum,
366 * Implements the RSET (reset state) command.
367 * Currently this just zeroes out the state buffer. If pointers to data
368 * allocated with mallok() are ever placed in the state buffer, we have to
369 * be sure to phree() them first!
371 * Set do_response to nonzero to output the SMTP RSET response code.
373 void smtp_rset(int do_response) {
377 * Our entire SMTP state is discarded when a RSET command is issued,
378 * but we need to preserve this one little piece of information, so
379 * we save it for later.
381 is_lmtp = SMTP->is_lmtp;
383 memset(SMTP, 0, sizeof(struct citsmtp));
386 * It is somewhat ambiguous whether we want to log out when a RSET
387 * command is issued. Here's the code to do it. It is commented out
388 * because some clients (such as Pine) issue RSET commands before
389 * each message, but still expect to be logged in.
391 * if (CC->logged_in) {
397 * Reinstate this little piece of information we saved (see above).
399 SMTP->is_lmtp = is_lmtp;
402 cprintf("250 2.0.0 Zap!\r\n");
407 * Clear out the portions of the state buffer that need to be cleared out
408 * after the DATA command finishes.
410 void smtp_data_clear(void) {
411 strcpy(SMTP->from, "");
412 strcpy(SMTP->recipients, "");
413 SMTP->number_of_recipients = 0;
414 SMTP->delivery_mode = 0;
415 SMTP->message_originated_locally = 0;
421 * Implements the "MAIL From:" command
423 void smtp_mail(char *argbuf) {
428 if (strlen(SMTP->from) != 0) {
429 cprintf("503 5.1.0 Only one sender permitted\r\n");
433 if (strncasecmp(argbuf, "From:", 5)) {
434 cprintf("501 5.1.7 Syntax error\r\n");
438 strcpy(SMTP->from, &argbuf[5]);
440 stripallbut(SMTP->from, '<', '>');
442 /* We used to reject empty sender names, until it was brought to our
443 * attention that RFC1123 5.2.9 requires that this be allowed. So now
444 * we allow it, but replace the empty string with a fake
445 * address so we don't have to contend with the empty string causing
446 * other code to fail when it's expecting something there.
448 if (strlen(SMTP->from) == 0) {
449 strcpy(SMTP->from, "someone@somewhere.org");
452 /* If this SMTP connection is from a logged-in user, force the 'from'
453 * to be the user's Internet e-mail address as Citadel knows it.
456 strcpy(SMTP->from, CC->cs_inet_email);
457 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
458 SMTP->message_originated_locally = 1;
462 else if (SMTP->is_lmtp) {
463 /* Bypass forgery checking for LMTP */
466 /* Otherwise, make sure outsiders aren't trying to forge mail from
470 process_rfc822_addr(SMTP->from, user, node, name);
471 if (CtdlHostAlias(node) != hostalias_nomatch) {
473 "You must log in to send mail from %s\r\n",
475 strcpy(SMTP->from, "");
480 cprintf("250 2.0.0 Sender ok\r\n");
486 * Implements the "RCPT To:" command
488 void smtp_rcpt(char *argbuf) {
490 char message_to_spammer[SIZ];
491 struct recptypes *valid = NULL;
493 if (strlen(SMTP->from) == 0) {
494 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
498 if (strncasecmp(argbuf, "To:", 3)) {
499 cprintf("501 5.1.7 Syntax error\r\n");
503 strcpy(recp, &argbuf[3]);
505 stripallbut(recp, '<', '>');
507 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
508 cprintf("452 4.5.3 Too many recipients\r\n");
513 if ( (!CC->logged_in)
514 && (!SMTP->is_lmtp) ) {
515 if (rbl_check(message_to_spammer)) {
516 cprintf("550 %s\r\n", message_to_spammer);
517 /* no need to phree(valid), it's not allocated yet */
522 valid = validate_recipients(recp);
523 if (valid->num_error > 0) {
524 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
529 if (valid->num_internet > 0) {
530 if ( (SMTP->message_originated_locally == 0)
531 && (SMTP->is_lmtp == 0) ) {
532 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
538 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
539 if (strlen(SMTP->recipients) > 0) {
540 strcat(SMTP->recipients, ",");
542 strcat(SMTP->recipients, recp);
543 SMTP->number_of_recipients += 1;
550 * Implements the DATA command
552 void smtp_data(void) {
554 struct CtdlMessage *msg;
557 struct recptypes *valid;
562 if (strlen(SMTP->from) == 0) {
563 cprintf("503 5.5.1 Need MAIL command first.\r\n");
567 if (SMTP->number_of_recipients < 1) {
568 cprintf("503 5.5.1 Need RCPT command first.\r\n");
572 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
574 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
577 if (body != NULL) snprintf(body, 4096,
578 "Received: from %s (%s [%s])\n"
586 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
589 "Unable to save message: internal error.\r\n");
593 lprintf(9, "Converting message...\n");
594 msg = convert_internet_message(body);
596 /* If the user is locally authenticated, FORCE the From: header to
597 * show up as the real sender. Yes, this violates the RFC standard,
598 * but IT MAKES SENSE. If you prefer strict RFC adherence over
599 * common sense, you can disable this in the configuration.
601 * We also set the "message room name" ('O' field) to MAILROOM
602 * (which is Mail> on most systems) to prevent it from getting set
603 * to something ugly like "0000058008.Sent Items>" when the message
604 * is read with a Citadel client.
606 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
607 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
608 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
609 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
610 if (msg->cm_fields['F'] != NULL) phree(msg->cm_fields['F']);
611 if (msg->cm_fields['O'] != NULL) phree(msg->cm_fields['O']);
612 msg->cm_fields['A'] = strdoop(CC->user.fullname);
613 msg->cm_fields['N'] = strdoop(config.c_nodename);
614 msg->cm_fields['H'] = strdoop(config.c_humannode);
615 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
616 msg->cm_fields['O'] = strdoop(MAILROOM);
619 /* Submit the message into the Citadel system. */
620 valid = validate_recipients(SMTP->recipients);
622 /* If there are modules that want to scan this message before final
623 * submission (such as virus checkers or spam filters), call them now
624 * and give them an opportunity to reject the message.
626 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
628 if (scan_errors > 0) { /* We don't want this message! */
630 if (msg->cm_fields['0'] == NULL) {
631 msg->cm_fields['0'] = strdoop(
632 "5.7.1 Message rejected by filter");
635 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
638 else { /* Ok, we'll accept this message. */
639 msgnum = CtdlSubmitMsg(msg, valid, "");
641 sprintf(result, "250 2.0.0 Message accepted.\r\n");
644 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
648 /* For SMTP and ESTMP, just print the result message. For LMTP, we
649 * have to print one result message for each recipient. Since there
650 * is nothing in Citadel which would cause different recipients to
651 * have different results, we can get away with just spitting out the
652 * same message once for each recipient.
655 for (i=0; i<SMTP->number_of_recipients; ++i) {
656 cprintf("%s", result);
660 cprintf("%s", result);
663 CtdlFreeMessage(msg);
665 smtp_data_clear(); /* clear out the buffers now */
670 * implements the STARTTLS command (Citadel API version)
673 void smtp_starttls(void)
675 char ok_response[SIZ];
676 char nosup_response[SIZ];
677 char error_response[SIZ];
680 "200 2.0.0 Begin TLS negotiation now\r\n");
681 sprintf(nosup_response,
682 "554 5.7.3 TLS not supported here\r\n");
683 sprintf(error_response,
684 "554 5.7.3 Internal error\r\n");
685 CtdlStartTLS(ok_response, nosup_response, error_response);
693 * Main command loop for SMTP sessions.
695 void smtp_command_loop(void) {
699 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
700 if (client_gets(cmdbuf) < 1) {
701 lprintf(3, "SMTP socket is broken. Ending session.\n");
705 lprintf(5, "SMTP: %s\n", cmdbuf);
706 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
708 lprintf(9, "CC->logged_in = %d\n", CC->logged_in);
710 if (SMTP->command_state == smtp_user) {
711 smtp_get_user(cmdbuf);
714 else if (SMTP->command_state == smtp_password) {
715 smtp_get_pass(cmdbuf);
718 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
719 smtp_auth(&cmdbuf[5]);
722 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
726 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
727 smtp_expn(&cmdbuf[5]);
730 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
731 smtp_hello(&cmdbuf[5], 0);
734 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
735 smtp_hello(&cmdbuf[5], 1);
738 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
739 smtp_hello(&cmdbuf[5], 2);
742 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
746 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
747 smtp_mail(&cmdbuf[5]);
750 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
751 cprintf("250 NOOP\r\n");
754 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
755 cprintf("221 Goodbye...\r\n");
760 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
761 smtp_rcpt(&cmdbuf[5]);
764 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
768 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
772 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
773 smtp_vrfy(&cmdbuf[5]);
777 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
785 /*****************************************************************************/
786 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
787 /*****************************************************************************/
794 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
797 void smtp_try(const char *key, const char *addr, int *status,
798 char *dsn, size_t n, long msgnum)
805 char user[SIZ], node[SIZ], name[SIZ];
811 size_t blocksize = 0;
814 /* Parse out the host portion of the recipient address */
815 process_rfc822_addr(addr, user, node, name);
817 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
820 /* Load the message out of the database into a temp file */
822 if (msg_fp == NULL) {
824 snprintf(dsn, n, "Error creating temporary file");
828 CtdlRedirectOutput(msg_fp, -1);
829 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
830 CtdlRedirectOutput(NULL, -1);
831 fseek(msg_fp, 0L, SEEK_END);
832 msg_size = ftell(msg_fp);
836 /* Extract something to send later in the 'MAIL From:' command */
837 strcpy(mailfrom, "");
841 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
842 if (!strncasecmp(buf, "From:", 5)) {
843 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
845 for (i=0; i<strlen(mailfrom); ++i) {
846 if (!isprint(mailfrom[i])) {
847 strcpy(&mailfrom[i], &mailfrom[i+1]);
852 /* Strip out parenthesized names */
855 for (i=0; i<strlen(mailfrom); ++i) {
856 if (mailfrom[i] == '(') lp = i;
857 if (mailfrom[i] == ')') rp = i;
859 if ((lp>0)&&(rp>lp)) {
860 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
863 /* Prefer brokketized names */
866 for (i=0; i<strlen(mailfrom); ++i) {
867 if (mailfrom[i] == '<') lp = i;
868 if (mailfrom[i] == '>') rp = i;
870 if ( (lp>=0) && (rp>lp) ) {
872 strcpy(mailfrom, &mailfrom[lp]);
877 } while (scan_done == 0);
878 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
880 /* Figure out what mail exchanger host we have to connect to */
881 num_mxhosts = getmx(mxhosts, node);
882 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
883 if (num_mxhosts < 1) {
885 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
890 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
891 extract(buf, mxhosts, mx);
892 lprintf(9, "Trying <%s>\n", buf);
893 sock = sock_connect(buf, "25", "tcp");
894 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
895 if (sock >= 0) lprintf(9, "Connected!\n");
896 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
900 *status = 4; /* dsn is already filled in */
904 /* Process the SMTP greeting from the server */
905 if (ml_sock_gets(sock, buf) < 0) {
907 strcpy(dsn, "Connection broken during SMTP conversation");
910 lprintf(9, "<%s\n", buf);
914 safestrncpy(dsn, &buf[4], 1023);
919 safestrncpy(dsn, &buf[4], 1023);
924 /* At this point we know we are talking to a real SMTP server */
926 /* Do a HELO command */
927 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
928 lprintf(9, ">%s", buf);
929 sock_write(sock, buf, strlen(buf));
930 if (ml_sock_gets(sock, buf) < 0) {
932 strcpy(dsn, "Connection broken during SMTP HELO");
935 lprintf(9, "<%s\n", buf);
939 safestrncpy(dsn, &buf[4], 1023);
944 safestrncpy(dsn, &buf[4], 1023);
950 /* HELO succeeded, now try the MAIL From: command */
951 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
952 lprintf(9, ">%s", buf);
953 sock_write(sock, buf, strlen(buf));
954 if (ml_sock_gets(sock, buf) < 0) {
956 strcpy(dsn, "Connection broken during SMTP MAIL");
959 lprintf(9, "<%s\n", buf);
963 safestrncpy(dsn, &buf[4], 1023);
968 safestrncpy(dsn, &buf[4], 1023);
974 /* MAIL succeeded, now try the RCPT To: command */
975 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
976 lprintf(9, ">%s", buf);
977 sock_write(sock, buf, strlen(buf));
978 if (ml_sock_gets(sock, buf) < 0) {
980 strcpy(dsn, "Connection broken during SMTP RCPT");
983 lprintf(9, "<%s\n", buf);
987 safestrncpy(dsn, &buf[4], 1023);
992 safestrncpy(dsn, &buf[4], 1023);
998 /* RCPT succeeded, now try the DATA command */
999 lprintf(9, ">DATA\n");
1000 sock_write(sock, "DATA\r\n", 6);
1001 if (ml_sock_gets(sock, buf) < 0) {
1003 strcpy(dsn, "Connection broken during SMTP DATA");
1006 lprintf(9, "<%s\n", buf);
1007 if (buf[0] != '3') {
1008 if (buf[0] == '4') {
1010 safestrncpy(dsn, &buf[4], 1023);
1015 safestrncpy(dsn, &buf[4], 1023);
1020 /* If we reach this point, the server is expecting data */
1022 while (msg_size > 0) {
1023 blocksize = sizeof(buf);
1024 if (blocksize > msg_size) blocksize = msg_size;
1025 fread(buf, blocksize, 1, msg_fp);
1026 sock_write(sock, buf, blocksize);
1027 msg_size -= blocksize;
1029 if (buf[blocksize-1] != 10) {
1030 lprintf(5, "Possible problem: message did not correctly "
1031 "terminate. (expecting 0x10, got 0x%02x)\n",
1035 sock_write(sock, ".\r\n", 3);
1036 if (ml_sock_gets(sock, buf) < 0) {
1038 strcpy(dsn, "Connection broken during SMTP message transmit");
1041 lprintf(9, "%s\n", buf);
1042 if (buf[0] != '2') {
1043 if (buf[0] == '4') {
1045 safestrncpy(dsn, &buf[4], 1023);
1050 safestrncpy(dsn, &buf[4], 1023);
1056 safestrncpy(dsn, &buf[4], 1023);
1059 lprintf(9, ">QUIT\n");
1060 sock_write(sock, "QUIT\r\n", 6);
1061 ml_sock_gets(sock, buf);
1062 lprintf(9, "<%s\n", buf);
1064 bail: if (msg_fp != NULL) fclose(msg_fp);
1072 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1073 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1074 * a "bounce" message (delivery status notification).
1076 void smtp_do_bounce(char *instr) {
1084 char bounceto[1024];
1085 int num_bounces = 0;
1086 int bounce_this = 0;
1087 long bounce_msgid = (-1);
1088 time_t submitted = 0L;
1089 struct CtdlMessage *bmsg = NULL;
1091 struct recptypes *valid;
1092 int successful_bounce = 0;
1094 lprintf(9, "smtp_do_bounce() called\n");
1095 strcpy(bounceto, "");
1097 lines = num_tokens(instr, '\n');
1100 /* See if it's time to give up on delivery of this message */
1101 for (i=0; i<lines; ++i) {
1102 extract_token(buf, instr, i, '\n');
1103 extract(key, buf, 0);
1104 extract(addr, buf, 1);
1105 if (!strcasecmp(key, "submitted")) {
1106 submitted = atol(addr);
1110 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1116 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1117 if (bmsg == NULL) return;
1118 memset(bmsg, 0, sizeof(struct CtdlMessage));
1120 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1121 bmsg->cm_anon_type = MES_NORMAL;
1122 bmsg->cm_format_type = 1;
1123 bmsg->cm_fields['A'] = strdoop("Citadel");
1124 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1125 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1127 if (give_up) bmsg->cm_fields['M'] = strdoop(
1128 "A message you sent could not be delivered to some or all of its recipients\n"
1129 "due to prolonged unavailability of its destination(s).\n"
1130 "Giving up on the following addresses:\n\n"
1133 else bmsg->cm_fields['M'] = strdoop(
1134 "A message you sent could not be delivered to some or all of its recipients.\n"
1135 "The following addresses were undeliverable:\n\n"
1139 * Now go through the instructions checking for stuff.
1141 for (i=0; i<lines; ++i) {
1142 extract_token(buf, instr, i, '\n');
1143 extract(key, buf, 0);
1144 extract(addr, buf, 1);
1145 status = extract_int(buf, 2);
1146 extract(dsn, buf, 3);
1149 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1150 key, addr, status, dsn);
1152 if (!strcasecmp(key, "bounceto")) {
1153 strcpy(bounceto, addr);
1157 (!strcasecmp(key, "local"))
1158 || (!strcasecmp(key, "remote"))
1159 || (!strcasecmp(key, "ignet"))
1160 || (!strcasecmp(key, "room"))
1162 if (status == 5) bounce_this = 1;
1163 if (give_up) bounce_this = 1;
1169 if (bmsg->cm_fields['M'] == NULL) {
1170 lprintf(2, "ERROR ... M field is null "
1171 "(%s:%d)\n", __FILE__, __LINE__);
1174 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1175 strlen(bmsg->cm_fields['M']) + 1024 );
1176 strcat(bmsg->cm_fields['M'], addr);
1177 strcat(bmsg->cm_fields['M'], ": ");
1178 strcat(bmsg->cm_fields['M'], dsn);
1179 strcat(bmsg->cm_fields['M'], "\n");
1181 remove_token(instr, i, '\n');
1187 /* Deliver the bounce if there's anything worth mentioning */
1188 lprintf(9, "num_bounces = %d\n", num_bounces);
1189 if (num_bounces > 0) {
1191 /* First try the user who sent the message */
1192 lprintf(9, "bounce to user? <%s>\n", bounceto);
1193 if (strlen(bounceto) == 0) {
1194 lprintf(7, "No bounce address specified\n");
1195 bounce_msgid = (-1L);
1198 /* Can we deliver the bounce to the original sender? */
1199 valid = validate_recipients(bounceto);
1200 if (valid != NULL) {
1201 if (valid->num_error == 0) {
1202 CtdlSubmitMsg(bmsg, valid, "");
1203 successful_bounce = 1;
1207 /* If not, post it in the Aide> room */
1208 if (successful_bounce == 0) {
1209 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1212 /* Free up the memory we used */
1213 if (valid != NULL) {
1218 CtdlFreeMessage(bmsg);
1219 lprintf(9, "Done processing bounces\n");
1224 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1225 * set of delivery instructions for completed deliveries and remove them.
1227 * It returns the number of incomplete deliveries remaining.
1229 int smtp_purge_completed_deliveries(char *instr) {
1240 lines = num_tokens(instr, '\n');
1241 for (i=0; i<lines; ++i) {
1242 extract_token(buf, instr, i, '\n');
1243 extract(key, buf, 0);
1244 extract(addr, buf, 1);
1245 status = extract_int(buf, 2);
1246 extract(dsn, buf, 3);
1251 (!strcasecmp(key, "local"))
1252 || (!strcasecmp(key, "remote"))
1253 || (!strcasecmp(key, "ignet"))
1254 || (!strcasecmp(key, "room"))
1256 if (status == 2) completed = 1;
1261 remove_token(instr, i, '\n');
1274 * Called by smtp_do_queue() to handle an individual message.
1276 void smtp_do_procmsg(long msgnum, void *userdata) {
1277 struct CtdlMessage *msg;
1279 char *results = NULL;
1287 long text_msgid = (-1);
1288 int incomplete_deliveries_remaining;
1289 time_t attempted = 0L;
1290 time_t last_attempted = 0L;
1291 time_t retry = SMTP_RETRY_INTERVAL;
1293 lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1295 msg = CtdlFetchMessage(msgnum);
1297 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1301 instr = strdoop(msg->cm_fields['M']);
1302 CtdlFreeMessage(msg);
1304 /* Strip out the headers amd any other non-instruction line */
1305 lines = num_tokens(instr, '\n');
1306 for (i=0; i<lines; ++i) {
1307 extract_token(buf, instr, i, '\n');
1308 if (num_tokens(buf, '|') < 2) {
1309 remove_token(instr, i, '\n');
1315 /* Learn the message ID and find out about recent delivery attempts */
1316 lines = num_tokens(instr, '\n');
1317 for (i=0; i<lines; ++i) {
1318 extract_token(buf, instr, i, '\n');
1319 extract(key, buf, 0);
1320 if (!strcasecmp(key, "msgid")) {
1321 text_msgid = extract_long(buf, 1);
1323 if (!strcasecmp(key, "retry")) {
1324 /* double the retry interval after each attempt */
1325 retry = extract_long(buf, 1) * 2L;
1326 if (retry > SMTP_RETRY_MAX) {
1327 retry = SMTP_RETRY_MAX;
1329 remove_token(instr, i, '\n');
1331 if (!strcasecmp(key, "attempted")) {
1332 attempted = extract_long(buf, 1);
1333 if (attempted > last_attempted)
1334 last_attempted = attempted;
1339 * Postpone delivery if we've already tried recently.
1341 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1342 lprintf(7, "Retry time not yet reached.\n");
1349 * Bail out if there's no actual message associated with this
1351 if (text_msgid < 0L) {
1352 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1357 /* Plow through the instructions looking for 'remote' directives and
1358 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1359 * were experienced and it's time to try again)
1361 lines = num_tokens(instr, '\n');
1362 for (i=0; i<lines; ++i) {
1363 extract_token(buf, instr, i, '\n');
1364 extract(key, buf, 0);
1365 extract(addr, buf, 1);
1366 status = extract_int(buf, 2);
1367 extract(dsn, buf, 3);
1368 if ( (!strcasecmp(key, "remote"))
1369 && ((status==0)||(status==3)||(status==4)) ) {
1371 /* Remove this "remote" instruction from the set,
1372 * but replace the set's final newline if
1373 * remove_token() stripped it. It has to be there.
1375 remove_token(instr, i, '\n');
1376 if (instr[strlen(instr)-1] != '\n') {
1377 strcat(instr, "\n");
1382 lprintf(9, "SMTP: Trying <%s>\n", addr);
1383 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1385 if (results == NULL) {
1386 results = mallok(1024);
1387 memset(results, 0, 1024);
1390 results = reallok(results,
1391 strlen(results) + 1024);
1393 snprintf(&results[strlen(results)], 1024,
1395 key, addr, status, dsn);
1400 if (results != NULL) {
1401 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1402 strcat(instr, results);
1407 /* Generate 'bounce' messages */
1408 smtp_do_bounce(instr);
1410 /* Go through the delivery list, deleting completed deliveries */
1411 incomplete_deliveries_remaining =
1412 smtp_purge_completed_deliveries(instr);
1416 * No delivery instructions remain, so delete both the instructions
1417 * message and the message message.
1419 if (incomplete_deliveries_remaining <= 0) {
1420 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1421 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1426 * Uncompleted delivery instructions remain, so delete the old
1427 * instructions and replace with the updated ones.
1429 if (incomplete_deliveries_remaining > 0) {
1430 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1431 msg = mallok(sizeof(struct CtdlMessage));
1432 memset(msg, 0, sizeof(struct CtdlMessage));
1433 msg->cm_magic = CTDLMESSAGE_MAGIC;
1434 msg->cm_anon_type = MES_NORMAL;
1435 msg->cm_format_type = FMT_RFC822;
1436 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1437 snprintf(msg->cm_fields['M'],
1439 "Content-type: %s\n\n%s\n"
1442 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1444 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1445 CtdlFreeMessage(msg);
1455 * Run through the queue sending out messages.
1457 void smtp_do_queue(void) {
1458 static int doing_queue = 0;
1461 * This is a simple concurrency check to make sure only one queue run
1462 * is done at a time. We could do this with a mutex, but since we
1463 * don't really require extremely fine granularity here, we'll do it
1464 * with a static variable instead.
1466 if (doing_queue) return;
1470 * Go ahead and run the queue
1472 lprintf(7, "SMTP: processing outbound queue\n");
1474 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1475 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1478 CtdlForEachMessage(MSGS_ALL, 0L,
1479 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1481 lprintf(7, "SMTP: queue run completed\n");
1488 /*****************************************************************************/
1489 /* SMTP UTILITY COMMANDS */
1490 /*****************************************************************************/
1492 void cmd_smtp(char *argbuf) {
1499 if (CtdlAccessCheck(ac_aide)) return;
1501 extract(cmd, argbuf, 0);
1503 if (!strcasecmp(cmd, "mx")) {
1504 extract(node, argbuf, 1);
1505 num_mxhosts = getmx(buf, node);
1506 cprintf("%d %d MX hosts listed for %s\n",
1507 LISTING_FOLLOWS, num_mxhosts, node);
1508 for (i=0; i<num_mxhosts; ++i) {
1509 extract(node, buf, i);
1510 cprintf("%s\n", node);
1516 else if (!strcasecmp(cmd, "runqueue")) {
1518 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1523 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1530 * Initialize the SMTP outbound queue
1532 void smtp_init_spoolout(void) {
1533 struct ctdlroom qrbuf;
1536 * Create the room. This will silently fail if the room already
1537 * exists, and that's perfectly ok, because we want it to exist.
1539 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0);
1542 * Make sure it's set to be a "system room" so it doesn't show up
1543 * in the <K>nown rooms list for Aides.
1545 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1546 qrbuf.QRflags2 |= QR2_SYSTEM;
1554 /*****************************************************************************/
1555 /* MODULE INITIALIZATION STUFF */
1556 /*****************************************************************************/
1559 char *serv_smtp_init(void)
1561 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1566 CtdlRegisterServiceHook(0, /* ...and locally */
1571 smtp_init_spoolout();
1572 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1573 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");