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;
98 enum { /* Command states for login authentication */
104 enum { /* Delivery modes */
109 #define SMTP ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
110 #define SMTP_RECPS ((char *)CtdlGetUserData(SYM_SMTP_RECPS))
111 #define SMTP_ROOMS ((char *)CtdlGetUserData(SYM_SMTP_ROOMS))
114 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
118 /*****************************************************************************/
119 /* SMTP SERVER (INBOUND) STUFF */
120 /*****************************************************************************/
126 * Here's where our SMTP session begins its happy day.
128 void smtp_greeting(void) {
130 strcpy(CC->cs_clientname, "SMTP session");
131 CC->internal_pgm = 1;
132 CC->cs_flags |= CS_STEALTH;
133 CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
134 CtdlAllocUserData(SYM_SMTP_RECPS, SIZ);
135 CtdlAllocUserData(SYM_SMTP_ROOMS, SIZ);
136 snprintf(SMTP_RECPS, SIZ, "%s", "");
137 snprintf(SMTP_ROOMS, SIZ, "%s", "");
139 cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn);
143 * SMTP MSA port requires authentication.
145 void smtp_msa_greeting(void) {
152 * LMTP is like SMTP but with some extra bonus footage added.
154 void lmtp_greeting(void) {
161 * Login greeting common to all auth methods
163 void smtp_auth_greeting(void) {
164 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
165 lprintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
166 CC->internal_pgm = 0;
167 CC->cs_flags &= ~CS_STEALTH;
172 * Implement HELO and EHLO commands.
174 * which_command: 0=HELO, 1=EHLO, 2=LHLO
176 void smtp_hello(char *argbuf, int which_command) {
178 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
180 if ( (which_command != 2) && (SMTP->is_lmtp) ) {
181 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
185 if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
186 cprintf("500 LHLO is only allowed when running LMTP\r\n");
190 if (which_command == 0) {
191 cprintf("250 Hello %s (%s [%s])\r\n",
198 if (which_command == 1) {
199 cprintf("250-Hello %s (%s [%s])\r\n",
206 cprintf("250-Greetings and joyous salutations.\r\n");
208 cprintf("250-HELP\r\n");
209 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
213 /* Only offer the PIPELINING command if TLS is inactive, because
214 * of flow control issues. Also, avoid offering TLS if TLS is
217 if (!CC->redirect_ssl) {
218 cprintf("250-PIPELINING\r\n");
219 cprintf("250-STARTTLS\r\n");
222 #else /* HAVE_OPENSSL */
224 /* Non SSL enabled server, so always offer PIPELINING. */
225 cprintf("250-PIPELINING\r\n");
227 #endif /* HAVE_OPENSSL */
229 cprintf("250-AUTH LOGIN PLAIN\r\n");
230 cprintf("250-AUTH=LOGIN PLAIN\r\n");
232 cprintf("250 ENHANCEDSTATUSCODES\r\n");
239 * Implement HELP command.
241 void smtp_help(void) {
242 cprintf("214-Commands accepted:\r\n");
243 cprintf("214- DATA\r\n");
244 cprintf("214- EHLO\r\n");
245 cprintf("214- EXPN\r\n");
246 cprintf("214- HELO\r\n");
247 cprintf("214- HELP\r\n");
248 cprintf("214- MAIL\r\n");
249 cprintf("214- NOOP\r\n");
250 cprintf("214- QUIT\r\n");
251 cprintf("214- RCPT\r\n");
252 cprintf("214- RSET\r\n");
253 cprintf("214- VRFY\r\n");
261 void smtp_get_user(char *argbuf) {
265 CtdlDecodeBase64(username, argbuf, SIZ);
266 lprintf(CTDL_DEBUG, "Trying <%s>\n", username);
267 if (CtdlLoginExistingUser(username) == login_ok) {
268 CtdlEncodeBase64(buf, "Password:", 9);
269 cprintf("334 %s\r\n", buf);
270 SMTP->command_state = smtp_password;
273 cprintf("500 5.7.0 No such user.\r\n");
274 SMTP->command_state = smtp_command;
282 void smtp_get_pass(char *argbuf) {
285 CtdlDecodeBase64(password, argbuf, SIZ);
286 lprintf(CTDL_DEBUG, "Trying <%s>\n", password);
287 if (CtdlTryPassword(password) == pass_ok) {
288 smtp_auth_greeting();
291 cprintf("535 5.7.0 Authentication failed.\r\n");
293 SMTP->command_state = smtp_command;
300 void smtp_auth(char *argbuf) {
303 char encoded_authstring[SIZ];
304 char decoded_authstring[SIZ];
310 cprintf("504 5.7.4 Already logged in.\r\n");
314 extract_token(method, argbuf, 0, ' ');
316 if (!strncasecmp(method, "login", 5) ) {
317 if (strlen(argbuf) >= 7) {
318 smtp_get_user(&argbuf[6]);
321 CtdlEncodeBase64(buf, "Username:", 9);
322 cprintf("334 %s\r\n", buf);
323 SMTP->command_state = smtp_user;
328 if (!strncasecmp(method, "plain", 5) ) {
329 extract_token(encoded_authstring, argbuf, 1, ' ');
330 CtdlDecodeBase64(decoded_authstring,
332 strlen(encoded_authstring) );
333 strcpy(ident, decoded_authstring);
334 strcpy(user, &decoded_authstring[strlen(ident) + 1] );
335 strcpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2] );
337 if (CtdlLoginExistingUser(user) == login_ok) {
338 if (CtdlTryPassword(pass) == pass_ok) {
339 smtp_auth_greeting();
343 cprintf("504 5.7.4 Authentication failed.\r\n");
346 if (strncasecmp(method, "login", 5) ) {
347 cprintf("504 5.7.4 Unknown authentication method.\r\n");
355 * Back end for smtp_vrfy() command
357 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
359 if (!fuzzy_match(us, SMTP->vrfy_match)) {
361 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
367 * Implements the VRFY (verify user name) command.
368 * Performs fuzzy match on full user names.
370 void smtp_vrfy(char *argbuf) {
371 SMTP->vrfy_count = 0;
372 strcpy(SMTP->vrfy_match, argbuf);
373 ForEachUser(smtp_vrfy_backend, NULL);
375 if (SMTP->vrfy_count < 1) {
376 cprintf("550 5.1.1 String does not match anything.\r\n");
378 else if (SMTP->vrfy_count == 1) {
379 cprintf("250 %s <cit%ld@%s>\r\n",
380 SMTP->vrfy_buffer.fullname,
381 SMTP->vrfy_buffer.usernum,
384 else if (SMTP->vrfy_count > 1) {
385 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
394 * Back end for smtp_expn() command
396 void smtp_expn_backend(struct ctdluser *us, void *data) {
398 if (!fuzzy_match(us, SMTP->vrfy_match)) {
400 if (SMTP->vrfy_count >= 1) {
401 cprintf("250-%s <cit%ld@%s>\r\n",
402 SMTP->vrfy_buffer.fullname,
403 SMTP->vrfy_buffer.usernum,
408 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
414 * Implements the EXPN (expand user name) command.
415 * Performs fuzzy match on full user names.
417 void smtp_expn(char *argbuf) {
418 SMTP->vrfy_count = 0;
419 strcpy(SMTP->vrfy_match, argbuf);
420 ForEachUser(smtp_expn_backend, NULL);
422 if (SMTP->vrfy_count < 1) {
423 cprintf("550 5.1.1 String does not match anything.\r\n");
425 else if (SMTP->vrfy_count >= 1) {
426 cprintf("250 %s <cit%ld@%s>\r\n",
427 SMTP->vrfy_buffer.fullname,
428 SMTP->vrfy_buffer.usernum,
435 * Implements the RSET (reset state) command.
436 * Currently this just zeroes out the state buffer. If pointers to data
437 * allocated with malloc() are ever placed in the state buffer, we have to
438 * be sure to free() them first!
440 * Set do_response to nonzero to output the SMTP RSET response code.
442 void smtp_rset(int do_response) {
446 * Our entire SMTP state is discarded when a RSET command is issued,
447 * but we need to preserve this one little piece of information, so
448 * we save it for later.
450 is_lmtp = SMTP->is_lmtp;
452 memset(SMTP, 0, sizeof(struct citsmtp));
455 * It is somewhat ambiguous whether we want to log out when a RSET
456 * command is issued. Here's the code to do it. It is commented out
457 * because some clients (such as Pine) issue RSET commands before
458 * each message, but still expect to be logged in.
460 * if (CC->logged_in) {
466 * Reinstate this little piece of information we saved (see above).
468 SMTP->is_lmtp = is_lmtp;
471 cprintf("250 2.0.0 Zap!\r\n");
476 * Clear out the portions of the state buffer that need to be cleared out
477 * after the DATA command finishes.
479 void smtp_data_clear(void) {
480 strcpy(SMTP->from, "");
481 strcpy(SMTP->recipients, "");
482 SMTP->number_of_recipients = 0;
483 SMTP->delivery_mode = 0;
484 SMTP->message_originated_locally = 0;
490 * Implements the "MAIL From:" command
492 void smtp_mail(char *argbuf) {
497 if (strlen(SMTP->from) != 0) {
498 cprintf("503 5.1.0 Only one sender permitted\r\n");
502 if (strncasecmp(argbuf, "From:", 5)) {
503 cprintf("501 5.1.7 Syntax error\r\n");
507 strcpy(SMTP->from, &argbuf[5]);
509 stripallbut(SMTP->from, '<', '>');
511 /* We used to reject empty sender names, until it was brought to our
512 * attention that RFC1123 5.2.9 requires that this be allowed. So now
513 * we allow it, but replace the empty string with a fake
514 * address so we don't have to contend with the empty string causing
515 * other code to fail when it's expecting something there.
517 if (strlen(SMTP->from) == 0) {
518 strcpy(SMTP->from, "someone@somewhere.org");
521 /* If this SMTP connection is from a logged-in user, force the 'from'
522 * to be the user's Internet e-mail address as Citadel knows it.
525 strcpy(SMTP->from, CC->cs_inet_email);
526 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
527 SMTP->message_originated_locally = 1;
531 else if (SMTP->is_lmtp) {
532 /* Bypass forgery checking for LMTP */
535 /* Otherwise, make sure outsiders aren't trying to forge mail from
539 process_rfc822_addr(SMTP->from, user, node, name);
540 if (CtdlHostAlias(node) != hostalias_nomatch) {
542 "You must log in to send mail from %s\r\n",
544 strcpy(SMTP->from, "");
549 cprintf("250 2.0.0 Sender ok\r\n");
555 * Implements the "RCPT To:" command
557 void smtp_rcpt(char *argbuf) {
559 char message_to_spammer[SIZ];
560 struct recptypes *valid = NULL;
562 if (strlen(SMTP->from) == 0) {
563 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
567 if (strncasecmp(argbuf, "To:", 3)) {
568 cprintf("501 5.1.7 Syntax error\r\n");
572 if ( (SMTP->is_msa) && (!CC->logged_in) ) {
574 "You must log in to send mail on this port.\r\n");
575 strcpy(SMTP->from, "");
579 strcpy(recp, &argbuf[3]);
581 stripallbut(recp, '<', '>');
583 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
584 cprintf("452 4.5.3 Too many recipients\r\n");
589 if ( (!CC->logged_in)
590 && (!SMTP->is_lmtp) ) {
591 if (rbl_check(message_to_spammer)) {
592 cprintf("550 %s\r\n", message_to_spammer);
593 /* no need to free(valid), it's not allocated yet */
598 valid = validate_recipients(recp);
599 if (valid->num_error > 0) {
600 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
605 if (valid->num_internet > 0) {
607 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
608 cprintf("551 5.7.1 <%s> - you do not have permission to send Internet mail\r\n", recp);
615 if (valid->num_internet > 0) {
616 if ( (SMTP->message_originated_locally == 0)
617 && (SMTP->is_lmtp == 0) ) {
618 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
624 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
625 if (strlen(SMTP->recipients) > 0) {
626 strcat(SMTP->recipients, ",");
628 strcat(SMTP->recipients, recp);
629 SMTP->number_of_recipients += 1;
636 * Implements the DATA command
638 void smtp_data(void) {
640 struct CtdlMessage *msg;
643 struct recptypes *valid;
648 if (strlen(SMTP->from) == 0) {
649 cprintf("503 5.5.1 Need MAIL command first.\r\n");
653 if (SMTP->number_of_recipients < 1) {
654 cprintf("503 5.5.1 Need RCPT command first.\r\n");
658 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
660 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
663 if (body != NULL) snprintf(body, 4096,
664 "Received: from %s (%s [%s])\n"
672 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
675 "Unable to save message: internal error.\r\n");
679 lprintf(CTDL_DEBUG, "Converting message...\n");
680 msg = convert_internet_message(body);
682 /* If the user is locally authenticated, FORCE the From: header to
683 * show up as the real sender. Yes, this violates the RFC standard,
684 * but IT MAKES SENSE. If you prefer strict RFC adherence over
685 * common sense, you can disable this in the configuration.
687 * We also set the "message room name" ('O' field) to MAILROOM
688 * (which is Mail> on most systems) to prevent it from getting set
689 * to something ugly like "0000058008.Sent Items>" when the message
690 * is read with a Citadel client.
692 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
693 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
694 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
695 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
696 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
697 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
698 msg->cm_fields['A'] = strdup(CC->user.fullname);
699 msg->cm_fields['N'] = strdup(config.c_nodename);
700 msg->cm_fields['H'] = strdup(config.c_humannode);
701 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
702 msg->cm_fields['O'] = strdup(MAILROOM);
705 /* Submit the message into the Citadel system. */
706 valid = validate_recipients(SMTP->recipients);
708 /* If there are modules that want to scan this message before final
709 * submission (such as virus checkers or spam filters), call them now
710 * and give them an opportunity to reject the message.
712 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
714 if (scan_errors > 0) { /* We don't want this message! */
716 if (msg->cm_fields['0'] == NULL) {
717 msg->cm_fields['0'] = strdup(
718 "5.7.1 Message rejected by filter");
721 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
724 else { /* Ok, we'll accept this message. */
725 msgnum = CtdlSubmitMsg(msg, valid, "");
727 sprintf(result, "250 2.0.0 Message accepted.\r\n");
730 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
734 /* For SMTP and ESTMP, just print the result message. For LMTP, we
735 * have to print one result message for each recipient. Since there
736 * is nothing in Citadel which would cause different recipients to
737 * have different results, we can get away with just spitting out the
738 * same message once for each recipient.
741 for (i=0; i<SMTP->number_of_recipients; ++i) {
742 cprintf("%s", result);
746 cprintf("%s", result);
749 CtdlFreeMessage(msg);
751 smtp_data_clear(); /* clear out the buffers now */
756 * implements the STARTTLS command (Citadel API version)
759 void smtp_starttls(void)
761 char ok_response[SIZ];
762 char nosup_response[SIZ];
763 char error_response[SIZ];
766 "200 2.0.0 Begin TLS negotiation now\r\n");
767 sprintf(nosup_response,
768 "554 5.7.3 TLS not supported here\r\n");
769 sprintf(error_response,
770 "554 5.7.3 Internal error\r\n");
771 CtdlStartTLS(ok_response, nosup_response, error_response);
779 * Main command loop for SMTP sessions.
781 void smtp_command_loop(void) {
785 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
786 if (client_gets(cmdbuf) < 1) {
787 lprintf(CTDL_CRIT, "SMTP socket is broken. Ending session.\n");
791 lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
792 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
794 if (SMTP->command_state == smtp_user) {
795 smtp_get_user(cmdbuf);
798 else if (SMTP->command_state == smtp_password) {
799 smtp_get_pass(cmdbuf);
802 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
803 smtp_auth(&cmdbuf[5]);
806 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
810 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
811 smtp_expn(&cmdbuf[5]);
814 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
815 smtp_hello(&cmdbuf[5], 0);
818 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
819 smtp_hello(&cmdbuf[5], 1);
822 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
823 smtp_hello(&cmdbuf[5], 2);
826 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
830 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
831 smtp_mail(&cmdbuf[5]);
834 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
835 cprintf("250 NOOP\r\n");
838 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
839 cprintf("221 Goodbye...\r\n");
844 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
845 smtp_rcpt(&cmdbuf[5]);
848 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
852 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
856 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
857 smtp_vrfy(&cmdbuf[5]);
861 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
870 /*****************************************************************************/
871 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
872 /*****************************************************************************/
879 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
882 void smtp_try(const char *key, const char *addr, int *status,
883 char *dsn, size_t n, long msgnum)
890 char user[SIZ], node[SIZ], name[SIZ];
896 size_t blocksize = 0;
899 /* Parse out the host portion of the recipient address */
900 process_rfc822_addr(addr, user, node, name);
902 lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
905 /* Load the message out of the database into a temp file */
907 if (msg_fp == NULL) {
909 snprintf(dsn, n, "Error creating temporary file");
913 CtdlRedirectOutput(msg_fp, -1);
914 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
915 CtdlRedirectOutput(NULL, -1);
916 fseek(msg_fp, 0L, SEEK_END);
917 msg_size = ftell(msg_fp);
921 /* Extract something to send later in the 'MAIL From:' command */
922 strcpy(mailfrom, "");
926 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
927 if (!strncasecmp(buf, "From:", 5)) {
928 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
930 for (i=0; i<strlen(mailfrom); ++i) {
931 if (!isprint(mailfrom[i])) {
932 strcpy(&mailfrom[i], &mailfrom[i+1]);
937 /* Strip out parenthesized names */
940 for (i=0; i<strlen(mailfrom); ++i) {
941 if (mailfrom[i] == '(') lp = i;
942 if (mailfrom[i] == ')') rp = i;
944 if ((lp>0)&&(rp>lp)) {
945 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
948 /* Prefer brokketized names */
951 for (i=0; i<strlen(mailfrom); ++i) {
952 if (mailfrom[i] == '<') lp = i;
953 if (mailfrom[i] == '>') rp = i;
955 if ( (lp>=0) && (rp>lp) ) {
957 strcpy(mailfrom, &mailfrom[lp]);
962 } while (scan_done == 0);
963 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
965 /* Figure out what mail exchanger host we have to connect to */
966 num_mxhosts = getmx(mxhosts, node);
967 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
968 if (num_mxhosts < 1) {
970 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
975 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
976 extract(buf, mxhosts, mx);
977 lprintf(CTDL_DEBUG, "Trying <%s>\n", buf);
978 sock = sock_connect(buf, "25", "tcp");
979 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
980 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
981 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
985 *status = 4; /* dsn is already filled in */
989 /* Process the SMTP greeting from the server */
990 if (ml_sock_gets(sock, buf) < 0) {
992 strcpy(dsn, "Connection broken during SMTP conversation");
995 lprintf(CTDL_DEBUG, "<%s\n", buf);
999 safestrncpy(dsn, &buf[4], 1023);
1004 safestrncpy(dsn, &buf[4], 1023);
1009 /* At this point we know we are talking to a real SMTP server */
1011 /* Do a HELO command */
1012 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1013 lprintf(CTDL_DEBUG, ">%s", buf);
1014 sock_write(sock, buf, strlen(buf));
1015 if (ml_sock_gets(sock, buf) < 0) {
1017 strcpy(dsn, "Connection broken during SMTP HELO");
1020 lprintf(CTDL_DEBUG, "<%s\n", buf);
1021 if (buf[0] != '2') {
1022 if (buf[0] == '4') {
1024 safestrncpy(dsn, &buf[4], 1023);
1029 safestrncpy(dsn, &buf[4], 1023);
1035 /* HELO succeeded, now try the MAIL From: command */
1036 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1037 lprintf(CTDL_DEBUG, ">%s", buf);
1038 sock_write(sock, buf, strlen(buf));
1039 if (ml_sock_gets(sock, buf) < 0) {
1041 strcpy(dsn, "Connection broken during SMTP MAIL");
1044 lprintf(CTDL_DEBUG, "<%s\n", buf);
1045 if (buf[0] != '2') {
1046 if (buf[0] == '4') {
1048 safestrncpy(dsn, &buf[4], 1023);
1053 safestrncpy(dsn, &buf[4], 1023);
1059 /* MAIL succeeded, now try the RCPT To: command */
1060 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
1061 lprintf(CTDL_DEBUG, ">%s", buf);
1062 sock_write(sock, buf, strlen(buf));
1063 if (ml_sock_gets(sock, buf) < 0) {
1065 strcpy(dsn, "Connection broken during SMTP RCPT");
1068 lprintf(CTDL_DEBUG, "<%s\n", buf);
1069 if (buf[0] != '2') {
1070 if (buf[0] == '4') {
1072 safestrncpy(dsn, &buf[4], 1023);
1077 safestrncpy(dsn, &buf[4], 1023);
1083 /* RCPT succeeded, now try the DATA command */
1084 lprintf(CTDL_DEBUG, ">DATA\n");
1085 sock_write(sock, "DATA\r\n", 6);
1086 if (ml_sock_gets(sock, buf) < 0) {
1088 strcpy(dsn, "Connection broken during SMTP DATA");
1091 lprintf(CTDL_DEBUG, "<%s\n", buf);
1092 if (buf[0] != '3') {
1093 if (buf[0] == '4') {
1095 safestrncpy(dsn, &buf[4], 1023);
1100 safestrncpy(dsn, &buf[4], 1023);
1105 /* If we reach this point, the server is expecting data */
1107 while (msg_size > 0) {
1108 blocksize = sizeof(buf);
1109 if (blocksize > msg_size) blocksize = msg_size;
1110 fread(buf, blocksize, 1, msg_fp);
1111 sock_write(sock, buf, blocksize);
1112 msg_size -= blocksize;
1114 if (buf[blocksize-1] != 10) {
1115 lprintf(CTDL_WARNING, "Possible problem: message did not "
1116 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1120 sock_write(sock, ".\r\n", 3);
1121 if (ml_sock_gets(sock, buf) < 0) {
1123 strcpy(dsn, "Connection broken during SMTP message transmit");
1126 lprintf(CTDL_DEBUG, "%s\n", buf);
1127 if (buf[0] != '2') {
1128 if (buf[0] == '4') {
1130 safestrncpy(dsn, &buf[4], 1023);
1135 safestrncpy(dsn, &buf[4], 1023);
1141 safestrncpy(dsn, &buf[4], 1023);
1144 lprintf(CTDL_DEBUG, ">QUIT\n");
1145 sock_write(sock, "QUIT\r\n", 6);
1146 ml_sock_gets(sock, buf);
1147 lprintf(CTDL_DEBUG, "<%s\n", buf);
1148 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1151 bail: if (msg_fp != NULL) fclose(msg_fp);
1159 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1160 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1161 * a "bounce" message (delivery status notification).
1163 void smtp_do_bounce(char *instr) {
1171 char bounceto[1024];
1172 int num_bounces = 0;
1173 int bounce_this = 0;
1174 long bounce_msgid = (-1);
1175 time_t submitted = 0L;
1176 struct CtdlMessage *bmsg = NULL;
1178 struct recptypes *valid;
1179 int successful_bounce = 0;
1181 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1182 strcpy(bounceto, "");
1184 lines = num_tokens(instr, '\n');
1187 /* See if it's time to give up on delivery of this message */
1188 for (i=0; i<lines; ++i) {
1189 extract_token(buf, instr, i, '\n');
1190 extract(key, buf, 0);
1191 extract(addr, buf, 1);
1192 if (!strcasecmp(key, "submitted")) {
1193 submitted = atol(addr);
1197 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1203 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1204 if (bmsg == NULL) return;
1205 memset(bmsg, 0, sizeof(struct CtdlMessage));
1207 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1208 bmsg->cm_anon_type = MES_NORMAL;
1209 bmsg->cm_format_type = 1;
1210 bmsg->cm_fields['A'] = strdup("Citadel");
1211 bmsg->cm_fields['O'] = strdup(MAILROOM);
1212 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1214 if (give_up) bmsg->cm_fields['M'] = strdup(
1215 "A message you sent could not be delivered to some or all of its recipients\n"
1216 "due to prolonged unavailability of its destination(s).\n"
1217 "Giving up on the following addresses:\n\n"
1220 else bmsg->cm_fields['M'] = strdup(
1221 "A message you sent could not be delivered to some or all of its recipients.\n"
1222 "The following addresses were undeliverable:\n\n"
1226 * Now go through the instructions checking for stuff.
1228 for (i=0; i<lines; ++i) {
1229 extract_token(buf, instr, i, '\n');
1230 extract(key, buf, 0);
1231 extract(addr, buf, 1);
1232 status = extract_int(buf, 2);
1233 extract(dsn, buf, 3);
1236 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1237 key, addr, status, dsn);
1239 if (!strcasecmp(key, "bounceto")) {
1240 strcpy(bounceto, addr);
1244 (!strcasecmp(key, "local"))
1245 || (!strcasecmp(key, "remote"))
1246 || (!strcasecmp(key, "ignet"))
1247 || (!strcasecmp(key, "room"))
1249 if (status == 5) bounce_this = 1;
1250 if (give_up) bounce_this = 1;
1256 if (bmsg->cm_fields['M'] == NULL) {
1257 lprintf(CTDL_ERR, "ERROR ... M field is null "
1258 "(%s:%d)\n", __FILE__, __LINE__);
1261 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1262 strlen(bmsg->cm_fields['M']) + 1024 );
1263 strcat(bmsg->cm_fields['M'], addr);
1264 strcat(bmsg->cm_fields['M'], ": ");
1265 strcat(bmsg->cm_fields['M'], dsn);
1266 strcat(bmsg->cm_fields['M'], "\n");
1268 remove_token(instr, i, '\n');
1274 /* Deliver the bounce if there's anything worth mentioning */
1275 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1276 if (num_bounces > 0) {
1278 /* First try the user who sent the message */
1279 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1280 if (strlen(bounceto) == 0) {
1281 lprintf(CTDL_ERR, "No bounce address specified\n");
1282 bounce_msgid = (-1L);
1285 /* Can we deliver the bounce to the original sender? */
1286 valid = validate_recipients(bounceto);
1287 if (valid != NULL) {
1288 if (valid->num_error == 0) {
1289 CtdlSubmitMsg(bmsg, valid, "");
1290 successful_bounce = 1;
1294 /* If not, post it in the Aide> room */
1295 if (successful_bounce == 0) {
1296 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1299 /* Free up the memory we used */
1300 if (valid != NULL) {
1305 CtdlFreeMessage(bmsg);
1306 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1311 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1312 * set of delivery instructions for completed deliveries and remove them.
1314 * It returns the number of incomplete deliveries remaining.
1316 int smtp_purge_completed_deliveries(char *instr) {
1327 lines = num_tokens(instr, '\n');
1328 for (i=0; i<lines; ++i) {
1329 extract_token(buf, instr, i, '\n');
1330 extract(key, buf, 0);
1331 extract(addr, buf, 1);
1332 status = extract_int(buf, 2);
1333 extract(dsn, buf, 3);
1338 (!strcasecmp(key, "local"))
1339 || (!strcasecmp(key, "remote"))
1340 || (!strcasecmp(key, "ignet"))
1341 || (!strcasecmp(key, "room"))
1343 if (status == 2) completed = 1;
1348 remove_token(instr, i, '\n');
1361 * Called by smtp_do_queue() to handle an individual message.
1363 void smtp_do_procmsg(long msgnum, void *userdata) {
1364 struct CtdlMessage *msg;
1366 char *results = NULL;
1374 long text_msgid = (-1);
1375 int incomplete_deliveries_remaining;
1376 time_t attempted = 0L;
1377 time_t last_attempted = 0L;
1378 time_t retry = SMTP_RETRY_INTERVAL;
1380 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1382 msg = CtdlFetchMessage(msgnum, 1);
1384 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1388 instr = strdup(msg->cm_fields['M']);
1389 CtdlFreeMessage(msg);
1391 /* Strip out the headers amd any other non-instruction line */
1392 lines = num_tokens(instr, '\n');
1393 for (i=0; i<lines; ++i) {
1394 extract_token(buf, instr, i, '\n');
1395 if (num_tokens(buf, '|') < 2) {
1396 remove_token(instr, i, '\n');
1402 /* Learn the message ID and find out about recent delivery attempts */
1403 lines = num_tokens(instr, '\n');
1404 for (i=0; i<lines; ++i) {
1405 extract_token(buf, instr, i, '\n');
1406 extract(key, buf, 0);
1407 if (!strcasecmp(key, "msgid")) {
1408 text_msgid = extract_long(buf, 1);
1410 if (!strcasecmp(key, "retry")) {
1411 /* double the retry interval after each attempt */
1412 retry = extract_long(buf, 1) * 2L;
1413 if (retry > SMTP_RETRY_MAX) {
1414 retry = SMTP_RETRY_MAX;
1416 remove_token(instr, i, '\n');
1418 if (!strcasecmp(key, "attempted")) {
1419 attempted = extract_long(buf, 1);
1420 if (attempted > last_attempted)
1421 last_attempted = attempted;
1426 * Postpone delivery if we've already tried recently.
1428 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1429 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1436 * Bail out if there's no actual message associated with this
1438 if (text_msgid < 0L) {
1439 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1444 /* Plow through the instructions looking for 'remote' directives and
1445 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1446 * were experienced and it's time to try again)
1448 lines = num_tokens(instr, '\n');
1449 for (i=0; i<lines; ++i) {
1450 extract_token(buf, instr, i, '\n');
1451 extract(key, buf, 0);
1452 extract(addr, buf, 1);
1453 status = extract_int(buf, 2);
1454 extract(dsn, buf, 3);
1455 if ( (!strcasecmp(key, "remote"))
1456 && ((status==0)||(status==3)||(status==4)) ) {
1458 /* Remove this "remote" instruction from the set,
1459 * but replace the set's final newline if
1460 * remove_token() stripped it. It has to be there.
1462 remove_token(instr, i, '\n');
1463 if (instr[strlen(instr)-1] != '\n') {
1464 strcat(instr, "\n");
1469 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1470 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1472 if (results == NULL) {
1473 results = malloc(1024);
1474 memset(results, 0, 1024);
1477 results = realloc(results,
1478 strlen(results) + 1024);
1480 snprintf(&results[strlen(results)], 1024,
1482 key, addr, status, dsn);
1487 if (results != NULL) {
1488 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1489 strcat(instr, results);
1494 /* Generate 'bounce' messages */
1495 smtp_do_bounce(instr);
1497 /* Go through the delivery list, deleting completed deliveries */
1498 incomplete_deliveries_remaining =
1499 smtp_purge_completed_deliveries(instr);
1503 * No delivery instructions remain, so delete both the instructions
1504 * message and the message message.
1506 if (incomplete_deliveries_remaining <= 0) {
1507 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1508 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1513 * Uncompleted delivery instructions remain, so delete the old
1514 * instructions and replace with the updated ones.
1516 if (incomplete_deliveries_remaining > 0) {
1517 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1518 msg = malloc(sizeof(struct CtdlMessage));
1519 memset(msg, 0, sizeof(struct CtdlMessage));
1520 msg->cm_magic = CTDLMESSAGE_MAGIC;
1521 msg->cm_anon_type = MES_NORMAL;
1522 msg->cm_format_type = FMT_RFC822;
1523 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1524 snprintf(msg->cm_fields['M'],
1526 "Content-type: %s\n\n%s\n"
1529 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1531 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1532 CtdlFreeMessage(msg);
1542 * Run through the queue sending out messages.
1544 void smtp_do_queue(void) {
1545 static int doing_queue = 0;
1548 * This is a simple concurrency check to make sure only one queue run
1549 * is done at a time. We could do this with a mutex, but since we
1550 * don't really require extremely fine granularity here, we'll do it
1551 * with a static variable instead.
1553 if (doing_queue) return;
1557 * Go ahead and run the queue
1559 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1561 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1562 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1565 CtdlForEachMessage(MSGS_ALL, 0L,
1566 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1568 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1575 /*****************************************************************************/
1576 /* SMTP UTILITY COMMANDS */
1577 /*****************************************************************************/
1579 void cmd_smtp(char *argbuf) {
1586 if (CtdlAccessCheck(ac_aide)) return;
1588 extract(cmd, argbuf, 0);
1590 if (!strcasecmp(cmd, "mx")) {
1591 extract(node, argbuf, 1);
1592 num_mxhosts = getmx(buf, node);
1593 cprintf("%d %d MX hosts listed for %s\n",
1594 LISTING_FOLLOWS, num_mxhosts, node);
1595 for (i=0; i<num_mxhosts; ++i) {
1596 extract(node, buf, i);
1597 cprintf("%s\n", node);
1603 else if (!strcasecmp(cmd, "runqueue")) {
1605 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1610 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1617 * Initialize the SMTP outbound queue
1619 void smtp_init_spoolout(void) {
1620 struct ctdlroom qrbuf;
1623 * Create the room. This will silently fail if the room already
1624 * exists, and that's perfectly ok, because we want it to exist.
1626 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0);
1629 * Make sure it's set to be a "system room" so it doesn't show up
1630 * in the <K>nown rooms list for Aides.
1632 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1633 qrbuf.QRflags2 |= QR2_SYSTEM;
1641 /*****************************************************************************/
1642 /* MODULE INITIALIZATION STUFF */
1643 /*****************************************************************************/
1646 char *serv_smtp_init(void)
1648 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1654 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1660 CtdlRegisterServiceHook(0, /* local LMTP */
1666 smtp_init_spoolout();
1667 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1668 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");