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);
144 * SMTPS is just like SMTP, except it goes crypto right away.
147 void smtps_greeting(void) {
148 CtdlStartTLS(NULL, NULL, NULL);
155 * SMTP MSA port requires authentication.
157 void smtp_msa_greeting(void) {
164 * LMTP is like SMTP but with some extra bonus footage added.
166 void lmtp_greeting(void) {
173 * Login greeting common to all auth methods
175 void smtp_auth_greeting(void) {
176 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
177 lprintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
178 CC->internal_pgm = 0;
179 CC->cs_flags &= ~CS_STEALTH;
184 * Implement HELO and EHLO commands.
186 * which_command: 0=HELO, 1=EHLO, 2=LHLO
188 void smtp_hello(char *argbuf, int which_command) {
190 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
192 if ( (which_command != 2) && (SMTP->is_lmtp) ) {
193 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
197 if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
198 cprintf("500 LHLO is only allowed when running LMTP\r\n");
202 if (which_command == 0) {
203 cprintf("250 Hello %s (%s [%s])\r\n",
210 if (which_command == 1) {
211 cprintf("250-Hello %s (%s [%s])\r\n",
218 cprintf("250-Greetings and joyous salutations.\r\n");
220 cprintf("250-HELP\r\n");
221 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
225 /* Only offer the PIPELINING command if TLS is inactive, because
226 * of flow control issues. Also, avoid offering TLS if TLS is
229 if (!CC->redirect_ssl) {
230 cprintf("250-PIPELINING\r\n");
231 cprintf("250-STARTTLS\r\n");
234 #else /* HAVE_OPENSSL */
236 /* Non SSL enabled server, so always offer PIPELINING. */
237 cprintf("250-PIPELINING\r\n");
239 #endif /* HAVE_OPENSSL */
241 cprintf("250-AUTH LOGIN PLAIN\r\n");
242 cprintf("250-AUTH=LOGIN PLAIN\r\n");
244 cprintf("250 ENHANCEDSTATUSCODES\r\n");
251 * Implement HELP command.
253 void smtp_help(void) {
254 cprintf("214-Commands accepted:\r\n");
255 cprintf("214- DATA\r\n");
256 cprintf("214- EHLO\r\n");
257 cprintf("214- EXPN\r\n");
258 cprintf("214- HELO\r\n");
259 cprintf("214- HELP\r\n");
260 cprintf("214- MAIL\r\n");
261 cprintf("214- NOOP\r\n");
262 cprintf("214- QUIT\r\n");
263 cprintf("214- RCPT\r\n");
264 cprintf("214- RSET\r\n");
265 cprintf("214- VRFY\r\n");
273 void smtp_get_user(char *argbuf) {
277 CtdlDecodeBase64(username, argbuf, SIZ);
278 lprintf(CTDL_DEBUG, "Trying <%s>\n", username);
279 if (CtdlLoginExistingUser(username) == login_ok) {
280 CtdlEncodeBase64(buf, "Password:", 9);
281 cprintf("334 %s\r\n", buf);
282 SMTP->command_state = smtp_password;
285 cprintf("500 5.7.0 No such user.\r\n");
286 SMTP->command_state = smtp_command;
294 void smtp_get_pass(char *argbuf) {
297 CtdlDecodeBase64(password, argbuf, SIZ);
298 lprintf(CTDL_DEBUG, "Trying <%s>\n", password);
299 if (CtdlTryPassword(password) == pass_ok) {
300 smtp_auth_greeting();
303 cprintf("535 5.7.0 Authentication failed.\r\n");
305 SMTP->command_state = smtp_command;
312 void smtp_auth(char *argbuf) {
315 char encoded_authstring[SIZ];
316 char decoded_authstring[SIZ];
322 cprintf("504 5.7.4 Already logged in.\r\n");
326 extract_token(method, argbuf, 0, ' ');
328 if (!strncasecmp(method, "login", 5) ) {
329 if (strlen(argbuf) >= 7) {
330 smtp_get_user(&argbuf[6]);
333 CtdlEncodeBase64(buf, "Username:", 9);
334 cprintf("334 %s\r\n", buf);
335 SMTP->command_state = smtp_user;
340 if (!strncasecmp(method, "plain", 5) ) {
341 extract_token(encoded_authstring, argbuf, 1, ' ');
342 CtdlDecodeBase64(decoded_authstring,
344 strlen(encoded_authstring) );
345 strcpy(ident, decoded_authstring);
346 strcpy(user, &decoded_authstring[strlen(ident) + 1] );
347 strcpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2] );
349 if (CtdlLoginExistingUser(user) == login_ok) {
350 if (CtdlTryPassword(pass) == pass_ok) {
351 smtp_auth_greeting();
355 cprintf("504 5.7.4 Authentication failed.\r\n");
358 if (strncasecmp(method, "login", 5) ) {
359 cprintf("504 5.7.4 Unknown authentication method.\r\n");
367 * Back end for smtp_vrfy() command
369 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
371 if (!fuzzy_match(us, SMTP->vrfy_match)) {
373 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
379 * Implements the VRFY (verify user name) command.
380 * Performs fuzzy match on full user names.
382 void smtp_vrfy(char *argbuf) {
383 SMTP->vrfy_count = 0;
384 strcpy(SMTP->vrfy_match, argbuf);
385 ForEachUser(smtp_vrfy_backend, NULL);
387 if (SMTP->vrfy_count < 1) {
388 cprintf("550 5.1.1 String does not match anything.\r\n");
390 else if (SMTP->vrfy_count == 1) {
391 cprintf("250 %s <cit%ld@%s>\r\n",
392 SMTP->vrfy_buffer.fullname,
393 SMTP->vrfy_buffer.usernum,
396 else if (SMTP->vrfy_count > 1) {
397 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
406 * Back end for smtp_expn() command
408 void smtp_expn_backend(struct ctdluser *us, void *data) {
410 if (!fuzzy_match(us, SMTP->vrfy_match)) {
412 if (SMTP->vrfy_count >= 1) {
413 cprintf("250-%s <cit%ld@%s>\r\n",
414 SMTP->vrfy_buffer.fullname,
415 SMTP->vrfy_buffer.usernum,
420 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
426 * Implements the EXPN (expand user name) command.
427 * Performs fuzzy match on full user names.
429 void smtp_expn(char *argbuf) {
430 SMTP->vrfy_count = 0;
431 strcpy(SMTP->vrfy_match, argbuf);
432 ForEachUser(smtp_expn_backend, NULL);
434 if (SMTP->vrfy_count < 1) {
435 cprintf("550 5.1.1 String does not match anything.\r\n");
437 else if (SMTP->vrfy_count >= 1) {
438 cprintf("250 %s <cit%ld@%s>\r\n",
439 SMTP->vrfy_buffer.fullname,
440 SMTP->vrfy_buffer.usernum,
447 * Implements the RSET (reset state) command.
448 * Currently this just zeroes out the state buffer. If pointers to data
449 * allocated with malloc() are ever placed in the state buffer, we have to
450 * be sure to free() them first!
452 * Set do_response to nonzero to output the SMTP RSET response code.
454 void smtp_rset(int do_response) {
458 * Our entire SMTP state is discarded when a RSET command is issued,
459 * but we need to preserve this one little piece of information, so
460 * we save it for later.
462 is_lmtp = SMTP->is_lmtp;
464 memset(SMTP, 0, sizeof(struct citsmtp));
467 * It is somewhat ambiguous whether we want to log out when a RSET
468 * command is issued. Here's the code to do it. It is commented out
469 * because some clients (such as Pine) issue RSET commands before
470 * each message, but still expect to be logged in.
472 * if (CC->logged_in) {
478 * Reinstate this little piece of information we saved (see above).
480 SMTP->is_lmtp = is_lmtp;
483 cprintf("250 2.0.0 Zap!\r\n");
488 * Clear out the portions of the state buffer that need to be cleared out
489 * after the DATA command finishes.
491 void smtp_data_clear(void) {
492 strcpy(SMTP->from, "");
493 strcpy(SMTP->recipients, "");
494 SMTP->number_of_recipients = 0;
495 SMTP->delivery_mode = 0;
496 SMTP->message_originated_locally = 0;
502 * Implements the "MAIL From:" command
504 void smtp_mail(char *argbuf) {
509 if (strlen(SMTP->from) != 0) {
510 cprintf("503 5.1.0 Only one sender permitted\r\n");
514 if (strncasecmp(argbuf, "From:", 5)) {
515 cprintf("501 5.1.7 Syntax error\r\n");
519 strcpy(SMTP->from, &argbuf[5]);
521 stripallbut(SMTP->from, '<', '>');
523 /* We used to reject empty sender names, until it was brought to our
524 * attention that RFC1123 5.2.9 requires that this be allowed. So now
525 * we allow it, but replace the empty string with a fake
526 * address so we don't have to contend with the empty string causing
527 * other code to fail when it's expecting something there.
529 if (strlen(SMTP->from) == 0) {
530 strcpy(SMTP->from, "someone@somewhere.org");
533 /* If this SMTP connection is from a logged-in user, force the 'from'
534 * to be the user's Internet e-mail address as Citadel knows it.
537 strcpy(SMTP->from, CC->cs_inet_email);
538 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
539 SMTP->message_originated_locally = 1;
543 else if (SMTP->is_lmtp) {
544 /* Bypass forgery checking for LMTP */
547 /* Otherwise, make sure outsiders aren't trying to forge mail from
551 process_rfc822_addr(SMTP->from, user, node, name);
552 if (CtdlHostAlias(node) != hostalias_nomatch) {
554 "You must log in to send mail from %s\r\n",
556 strcpy(SMTP->from, "");
561 cprintf("250 2.0.0 Sender ok\r\n");
567 * Implements the "RCPT To:" command
569 void smtp_rcpt(char *argbuf) {
571 char message_to_spammer[SIZ];
572 struct recptypes *valid = NULL;
574 if (strlen(SMTP->from) == 0) {
575 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
579 if (strncasecmp(argbuf, "To:", 3)) {
580 cprintf("501 5.1.7 Syntax error\r\n");
584 if ( (SMTP->is_msa) && (!CC->logged_in) ) {
586 "You must log in to send mail on this port.\r\n");
587 strcpy(SMTP->from, "");
591 strcpy(recp, &argbuf[3]);
593 stripallbut(recp, '<', '>');
595 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
596 cprintf("452 4.5.3 Too many recipients\r\n");
601 if ( (!CC->logged_in)
602 && (!SMTP->is_lmtp) ) {
603 if (rbl_check(message_to_spammer)) {
604 cprintf("550 %s\r\n", message_to_spammer);
605 /* no need to free(valid), it's not allocated yet */
610 valid = validate_recipients(recp);
611 if (valid->num_error > 0) {
612 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
617 if (valid->num_internet > 0) {
619 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
620 cprintf("551 5.7.1 <%s> - you do not have permission to send Internet mail\r\n", recp);
627 if (valid->num_internet > 0) {
628 if ( (SMTP->message_originated_locally == 0)
629 && (SMTP->is_lmtp == 0) ) {
630 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
636 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
637 if (strlen(SMTP->recipients) > 0) {
638 strcat(SMTP->recipients, ",");
640 strcat(SMTP->recipients, recp);
641 SMTP->number_of_recipients += 1;
648 * Implements the DATA command
650 void smtp_data(void) {
652 struct CtdlMessage *msg;
655 struct recptypes *valid;
660 if (strlen(SMTP->from) == 0) {
661 cprintf("503 5.5.1 Need MAIL command first.\r\n");
665 if (SMTP->number_of_recipients < 1) {
666 cprintf("503 5.5.1 Need RCPT command first.\r\n");
670 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
672 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
675 if (body != NULL) snprintf(body, 4096,
676 "Received: from %s (%s [%s])\n"
684 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
687 "Unable to save message: internal error.\r\n");
691 lprintf(CTDL_DEBUG, "Converting message...\n");
692 msg = convert_internet_message(body);
694 /* If the user is locally authenticated, FORCE the From: header to
695 * show up as the real sender. Yes, this violates the RFC standard,
696 * but IT MAKES SENSE. If you prefer strict RFC adherence over
697 * common sense, you can disable this in the configuration.
699 * We also set the "message room name" ('O' field) to MAILROOM
700 * (which is Mail> on most systems) to prevent it from getting set
701 * to something ugly like "0000058008.Sent Items>" when the message
702 * is read with a Citadel client.
704 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
705 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
706 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
707 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
708 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
709 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
710 msg->cm_fields['A'] = strdup(CC->user.fullname);
711 msg->cm_fields['N'] = strdup(config.c_nodename);
712 msg->cm_fields['H'] = strdup(config.c_humannode);
713 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
714 msg->cm_fields['O'] = strdup(MAILROOM);
717 /* Submit the message into the Citadel system. */
718 valid = validate_recipients(SMTP->recipients);
720 /* If there are modules that want to scan this message before final
721 * submission (such as virus checkers or spam filters), call them now
722 * and give them an opportunity to reject the message.
724 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
726 if (scan_errors > 0) { /* We don't want this message! */
728 if (msg->cm_fields['0'] == NULL) {
729 msg->cm_fields['0'] = strdup(
730 "5.7.1 Message rejected by filter");
733 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
736 else { /* Ok, we'll accept this message. */
737 msgnum = CtdlSubmitMsg(msg, valid, "");
739 sprintf(result, "250 2.0.0 Message accepted.\r\n");
742 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
746 /* For SMTP and ESTMP, just print the result message. For LMTP, we
747 * have to print one result message for each recipient. Since there
748 * is nothing in Citadel which would cause different recipients to
749 * have different results, we can get away with just spitting out the
750 * same message once for each recipient.
753 for (i=0; i<SMTP->number_of_recipients; ++i) {
754 cprintf("%s", result);
758 cprintf("%s", result);
761 CtdlFreeMessage(msg);
763 smtp_data_clear(); /* clear out the buffers now */
768 * implements the STARTTLS command (Citadel API version)
771 void smtp_starttls(void)
773 char ok_response[SIZ];
774 char nosup_response[SIZ];
775 char error_response[SIZ];
778 "200 2.0.0 Begin TLS negotiation now\r\n");
779 sprintf(nosup_response,
780 "554 5.7.3 TLS not supported here\r\n");
781 sprintf(error_response,
782 "554 5.7.3 Internal error\r\n");
783 CtdlStartTLS(ok_response, nosup_response, error_response);
791 * Main command loop for SMTP sessions.
793 void smtp_command_loop(void) {
797 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
798 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
799 lprintf(CTDL_CRIT, "SMTP socket is broken. Ending session.\n");
803 lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
804 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
806 if (SMTP->command_state == smtp_user) {
807 smtp_get_user(cmdbuf);
810 else if (SMTP->command_state == smtp_password) {
811 smtp_get_pass(cmdbuf);
814 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
815 smtp_auth(&cmdbuf[5]);
818 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
822 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
823 smtp_expn(&cmdbuf[5]);
826 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
827 smtp_hello(&cmdbuf[5], 0);
830 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
831 smtp_hello(&cmdbuf[5], 1);
834 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
835 smtp_hello(&cmdbuf[5], 2);
838 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
842 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
843 smtp_mail(&cmdbuf[5]);
846 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
847 cprintf("250 NOOP\r\n");
850 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
851 cprintf("221 Goodbye...\r\n");
856 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
857 smtp_rcpt(&cmdbuf[5]);
860 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
864 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
868 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
869 smtp_vrfy(&cmdbuf[5]);
873 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
882 /*****************************************************************************/
883 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
884 /*****************************************************************************/
891 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
894 void smtp_try(const char *key, const char *addr, int *status,
895 char *dsn, size_t n, long msgnum)
902 char user[SIZ], node[SIZ], name[SIZ];
911 /* Parse out the host portion of the recipient address */
912 process_rfc822_addr(addr, user, node, name);
914 lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
917 /* Load the message out of the database */
918 CC->redirect_buffer = malloc(SIZ);
919 CC->redirect_len = 0;
920 CC->redirect_alloc = SIZ;
921 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
922 CC->redirect_buffer[CC->redirect_len] = 0;
923 msgtext = CC->redirect_buffer;
924 msg_size = CC->redirect_len;
925 CC->redirect_buffer = NULL;
926 CC->redirect_len = 0;
927 CC->redirect_alloc = 0;
929 /* Extract something to send later in the 'MAIL From:' command */
930 strcpy(mailfrom, "");
934 if (ptr = memreadline(ptr, buf, sizeof buf), ptr==NULL) {
937 if (!strncasecmp(buf, "From:", 5)) {
938 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
940 for (i=0; i<strlen(mailfrom); ++i) {
941 if (!isprint(mailfrom[i])) {
942 strcpy(&mailfrom[i], &mailfrom[i+1]);
947 /* Strip out parenthesized names */
950 for (i=0; i<strlen(mailfrom); ++i) {
951 if (mailfrom[i] == '(') lp = i;
952 if (mailfrom[i] == ')') rp = i;
954 if ((lp>0)&&(rp>lp)) {
955 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
958 /* Prefer brokketized names */
961 for (i=0; i<strlen(mailfrom); ++i) {
962 if (mailfrom[i] == '<') lp = i;
963 if (mailfrom[i] == '>') rp = i;
965 if ( (lp>=0) && (rp>lp) ) {
967 strcpy(mailfrom, &mailfrom[lp]);
972 } while (scan_done == 0);
973 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
975 /* Figure out what mail exchanger host we have to connect to */
976 num_mxhosts = getmx(mxhosts, node);
977 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
978 if (num_mxhosts < 1) {
980 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
985 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
986 extract(buf, mxhosts, mx);
987 lprintf(CTDL_DEBUG, "Trying <%s>\n", buf);
988 sock = sock_connect(buf, "25", "tcp");
989 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
990 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
991 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
995 *status = 4; /* dsn is already filled in */
999 /* Process the SMTP greeting from the server */
1000 if (ml_sock_gets(sock, buf) < 0) {
1002 strcpy(dsn, "Connection broken during SMTP conversation");
1005 lprintf(CTDL_DEBUG, "<%s\n", buf);
1006 if (buf[0] != '2') {
1007 if (buf[0] == '4') {
1009 safestrncpy(dsn, &buf[4], 1023);
1014 safestrncpy(dsn, &buf[4], 1023);
1019 /* At this point we know we are talking to a real SMTP server */
1021 /* Do a HELO command */
1022 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1023 lprintf(CTDL_DEBUG, ">%s", buf);
1024 sock_write(sock, buf, strlen(buf));
1025 if (ml_sock_gets(sock, buf) < 0) {
1027 strcpy(dsn, "Connection broken during SMTP HELO");
1030 lprintf(CTDL_DEBUG, "<%s\n", buf);
1031 if (buf[0] != '2') {
1032 if (buf[0] == '4') {
1034 safestrncpy(dsn, &buf[4], 1023);
1039 safestrncpy(dsn, &buf[4], 1023);
1044 /* HELO succeeded, now try the MAIL From: command */
1045 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1046 lprintf(CTDL_DEBUG, ">%s", buf);
1047 sock_write(sock, buf, strlen(buf));
1048 if (ml_sock_gets(sock, buf) < 0) {
1050 strcpy(dsn, "Connection broken during SMTP MAIL");
1053 lprintf(CTDL_DEBUG, "<%s\n", buf);
1054 if (buf[0] != '2') {
1055 if (buf[0] == '4') {
1057 safestrncpy(dsn, &buf[4], 1023);
1062 safestrncpy(dsn, &buf[4], 1023);
1067 /* MAIL succeeded, now try the RCPT To: command */
1068 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
1069 lprintf(CTDL_DEBUG, ">%s", buf);
1070 sock_write(sock, buf, strlen(buf));
1071 if (ml_sock_gets(sock, buf) < 0) {
1073 strcpy(dsn, "Connection broken during SMTP RCPT");
1076 lprintf(CTDL_DEBUG, "<%s\n", buf);
1077 if (buf[0] != '2') {
1078 if (buf[0] == '4') {
1080 safestrncpy(dsn, &buf[4], 1023);
1085 safestrncpy(dsn, &buf[4], 1023);
1090 /* RCPT succeeded, now try the DATA command */
1091 lprintf(CTDL_DEBUG, ">DATA\n");
1092 sock_write(sock, "DATA\r\n", 6);
1093 if (ml_sock_gets(sock, buf) < 0) {
1095 strcpy(dsn, "Connection broken during SMTP DATA");
1098 lprintf(CTDL_DEBUG, "<%s\n", buf);
1099 if (buf[0] != '3') {
1100 if (buf[0] == '4') {
1102 safestrncpy(dsn, &buf[4], 1023);
1107 safestrncpy(dsn, &buf[4], 1023);
1112 /* If we reach this point, the server is expecting data */
1113 sock_write(sock, msgtext, msg_size);
1114 if (msgtext[msg_size-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: free(msgtext);
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 );
1530 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1531 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, VIEW_MAILBOX);
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 */
1655 CtdlRegisterServiceHook(config.c_smtps_port,
1662 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1668 CtdlRegisterServiceHook(0, /* local LMTP */
1674 smtp_init_spoolout();
1675 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1676 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");