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 * Login greeting common to all auth methods
153 void smtp_auth_greeting(void) {
154 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
155 lprintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
156 CC->internal_pgm = 0;
157 CC->cs_flags &= ~CS_STEALTH;
162 * Implement HELO and EHLO commands.
164 * which_command: 0=HELO, 1=EHLO, 2=LHLO
166 void smtp_hello(char *argbuf, int which_command) {
168 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
170 if ( (which_command != 2) && (SMTP->is_lmtp) ) {
171 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
175 if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
176 cprintf("500 LHLO is only allowed when running LMTP\r\n");
180 if (which_command == 0) {
181 cprintf("250 Hello %s (%s [%s])\r\n",
188 if (which_command == 1) {
189 cprintf("250-Hello %s (%s [%s])\r\n",
196 cprintf("250-Greetings and joyous salutations.\r\n");
198 cprintf("250-HELP\r\n");
199 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
201 /* Only offer the PIPELINING command if TLS is inactive, because
202 * of flow control issues. Also, avoid offering TLS if TLS is
205 if (!CC->redirect_ssl) {
206 cprintf("250-PIPELINING\r\n");
208 cprintf("250-STARTTLS\r\n");
212 cprintf("250-AUTH LOGIN PLAIN\r\n");
213 cprintf("250-AUTH=LOGIN PLAIN\r\n");
215 cprintf("250 ENHANCEDSTATUSCODES\r\n");
222 * Implement HELP command.
224 void smtp_help(void) {
225 cprintf("214-Commands accepted:\r\n");
226 cprintf("214- DATA\r\n");
227 cprintf("214- EHLO\r\n");
228 cprintf("214- EXPN\r\n");
229 cprintf("214- HELO\r\n");
230 cprintf("214- HELP\r\n");
231 cprintf("214- MAIL\r\n");
232 cprintf("214- NOOP\r\n");
233 cprintf("214- QUIT\r\n");
234 cprintf("214- RCPT\r\n");
235 cprintf("214- RSET\r\n");
236 cprintf("214- VRFY\r\n");
244 void smtp_get_user(char *argbuf) {
248 CtdlDecodeBase64(username, argbuf, SIZ);
249 lprintf(CTDL_DEBUG, "Trying <%s>\n", username);
250 if (CtdlLoginExistingUser(username) == login_ok) {
251 CtdlEncodeBase64(buf, "Password:", 9);
252 cprintf("334 %s\r\n", buf);
253 SMTP->command_state = smtp_password;
256 cprintf("500 5.7.0 No such user.\r\n");
257 SMTP->command_state = smtp_command;
265 void smtp_get_pass(char *argbuf) {
268 CtdlDecodeBase64(password, argbuf, SIZ);
269 lprintf(CTDL_DEBUG, "Trying <%s>\n", password);
270 if (CtdlTryPassword(password) == pass_ok) {
271 smtp_auth_greeting();
274 cprintf("535 5.7.0 Authentication failed.\r\n");
276 SMTP->command_state = smtp_command;
283 void smtp_auth(char *argbuf) {
286 char encoded_authstring[SIZ];
287 char decoded_authstring[SIZ];
293 cprintf("504 5.7.4 Already logged in.\r\n");
297 extract_token(method, argbuf, 0, ' ');
299 if (!strncasecmp(method, "login", 5) ) {
300 if (strlen(argbuf) >= 7) {
301 smtp_get_user(&argbuf[6]);
304 CtdlEncodeBase64(buf, "Username:", 9);
305 cprintf("334 %s\r\n", buf);
306 SMTP->command_state = smtp_user;
311 if (!strncasecmp(method, "plain", 5) ) {
312 extract_token(encoded_authstring, argbuf, 1, ' ');
313 CtdlDecodeBase64(decoded_authstring,
315 strlen(encoded_authstring) );
316 strcpy(ident, decoded_authstring);
317 strcpy(user, &decoded_authstring[strlen(ident) + 1] );
318 strcpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2] );
320 if (CtdlLoginExistingUser(user) == login_ok) {
321 if (CtdlTryPassword(pass) == pass_ok) {
322 smtp_auth_greeting();
326 cprintf("504 5.7.4 Authentication failed.\r\n");
329 if (strncasecmp(method, "login", 5) ) {
330 cprintf("504 5.7.4 Unknown authentication method.\r\n");
338 * Back end for smtp_vrfy() command
340 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
342 if (!fuzzy_match(us, SMTP->vrfy_match)) {
344 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
350 * Implements the VRFY (verify user name) command.
351 * Performs fuzzy match on full user names.
353 void smtp_vrfy(char *argbuf) {
354 SMTP->vrfy_count = 0;
355 strcpy(SMTP->vrfy_match, argbuf);
356 ForEachUser(smtp_vrfy_backend, NULL);
358 if (SMTP->vrfy_count < 1) {
359 cprintf("550 5.1.1 String does not match anything.\r\n");
361 else if (SMTP->vrfy_count == 1) {
362 cprintf("250 %s <cit%ld@%s>\r\n",
363 SMTP->vrfy_buffer.fullname,
364 SMTP->vrfy_buffer.usernum,
367 else if (SMTP->vrfy_count > 1) {
368 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
377 * Back end for smtp_expn() command
379 void smtp_expn_backend(struct ctdluser *us, void *data) {
381 if (!fuzzy_match(us, SMTP->vrfy_match)) {
383 if (SMTP->vrfy_count >= 1) {
384 cprintf("250-%s <cit%ld@%s>\r\n",
385 SMTP->vrfy_buffer.fullname,
386 SMTP->vrfy_buffer.usernum,
391 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
397 * Implements the EXPN (expand user name) command.
398 * Performs fuzzy match on full user names.
400 void smtp_expn(char *argbuf) {
401 SMTP->vrfy_count = 0;
402 strcpy(SMTP->vrfy_match, argbuf);
403 ForEachUser(smtp_expn_backend, NULL);
405 if (SMTP->vrfy_count < 1) {
406 cprintf("550 5.1.1 String does not match anything.\r\n");
408 else if (SMTP->vrfy_count >= 1) {
409 cprintf("250 %s <cit%ld@%s>\r\n",
410 SMTP->vrfy_buffer.fullname,
411 SMTP->vrfy_buffer.usernum,
418 * Implements the RSET (reset state) command.
419 * Currently this just zeroes out the state buffer. If pointers to data
420 * allocated with malloc() are ever placed in the state buffer, we have to
421 * be sure to free() them first!
423 * Set do_response to nonzero to output the SMTP RSET response code.
425 void smtp_rset(int do_response) {
429 * Our entire SMTP state is discarded when a RSET command is issued,
430 * but we need to preserve this one little piece of information, so
431 * we save it for later.
433 is_lmtp = SMTP->is_lmtp;
435 memset(SMTP, 0, sizeof(struct citsmtp));
438 * It is somewhat ambiguous whether we want to log out when a RSET
439 * command is issued. Here's the code to do it. It is commented out
440 * because some clients (such as Pine) issue RSET commands before
441 * each message, but still expect to be logged in.
443 * if (CC->logged_in) {
449 * Reinstate this little piece of information we saved (see above).
451 SMTP->is_lmtp = is_lmtp;
454 cprintf("250 2.0.0 Zap!\r\n");
459 * Clear out the portions of the state buffer that need to be cleared out
460 * after the DATA command finishes.
462 void smtp_data_clear(void) {
463 strcpy(SMTP->from, "");
464 strcpy(SMTP->recipients, "");
465 SMTP->number_of_recipients = 0;
466 SMTP->delivery_mode = 0;
467 SMTP->message_originated_locally = 0;
473 * Implements the "MAIL From:" command
475 void smtp_mail(char *argbuf) {
480 if (strlen(SMTP->from) != 0) {
481 cprintf("503 5.1.0 Only one sender permitted\r\n");
485 if (strncasecmp(argbuf, "From:", 5)) {
486 cprintf("501 5.1.7 Syntax error\r\n");
490 strcpy(SMTP->from, &argbuf[5]);
492 stripallbut(SMTP->from, '<', '>');
494 /* We used to reject empty sender names, until it was brought to our
495 * attention that RFC1123 5.2.9 requires that this be allowed. So now
496 * we allow it, but replace the empty string with a fake
497 * address so we don't have to contend with the empty string causing
498 * other code to fail when it's expecting something there.
500 if (strlen(SMTP->from) == 0) {
501 strcpy(SMTP->from, "someone@somewhere.org");
504 /* If this SMTP connection is from a logged-in user, force the 'from'
505 * to be the user's Internet e-mail address as Citadel knows it.
508 strcpy(SMTP->from, CC->cs_inet_email);
509 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
510 SMTP->message_originated_locally = 1;
514 else if (SMTP->is_lmtp) {
515 /* Bypass forgery checking for LMTP */
518 /* Otherwise, make sure outsiders aren't trying to forge mail from
522 process_rfc822_addr(SMTP->from, user, node, name);
523 if (CtdlHostAlias(node) != hostalias_nomatch) {
525 "You must log in to send mail from %s\r\n",
527 strcpy(SMTP->from, "");
532 cprintf("250 2.0.0 Sender ok\r\n");
538 * Implements the "RCPT To:" command
540 void smtp_rcpt(char *argbuf) {
542 char message_to_spammer[SIZ];
543 struct recptypes *valid = NULL;
545 if (strlen(SMTP->from) == 0) {
546 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
550 if (strncasecmp(argbuf, "To:", 3)) {
551 cprintf("501 5.1.7 Syntax error\r\n");
555 strcpy(recp, &argbuf[3]);
557 stripallbut(recp, '<', '>');
559 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
560 cprintf("452 4.5.3 Too many recipients\r\n");
565 if ( (!CC->logged_in)
566 && (!SMTP->is_lmtp) ) {
567 if (rbl_check(message_to_spammer)) {
568 cprintf("550 %s\r\n", message_to_spammer);
569 /* no need to free(valid), it's not allocated yet */
574 valid = validate_recipients(recp);
575 if (valid->num_error > 0) {
576 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
581 if (valid->num_internet > 0) {
583 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
584 cprintf("551 5.7.1 <%s> - you do not have permission to send Internet mail\r\n", recp);
591 if (valid->num_internet > 0) {
592 if ( (SMTP->message_originated_locally == 0)
593 && (SMTP->is_lmtp == 0) ) {
594 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
600 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
601 if (strlen(SMTP->recipients) > 0) {
602 strcat(SMTP->recipients, ",");
604 strcat(SMTP->recipients, recp);
605 SMTP->number_of_recipients += 1;
612 * Implements the DATA command
614 void smtp_data(void) {
616 struct CtdlMessage *msg;
619 struct recptypes *valid;
624 if (strlen(SMTP->from) == 0) {
625 cprintf("503 5.5.1 Need MAIL command first.\r\n");
629 if (SMTP->number_of_recipients < 1) {
630 cprintf("503 5.5.1 Need RCPT command first.\r\n");
634 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
636 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
639 if (body != NULL) snprintf(body, 4096,
640 "Received: from %s (%s [%s])\n"
648 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
651 "Unable to save message: internal error.\r\n");
655 lprintf(CTDL_DEBUG, "Converting message...\n");
656 msg = convert_internet_message(body);
658 /* If the user is locally authenticated, FORCE the From: header to
659 * show up as the real sender. Yes, this violates the RFC standard,
660 * but IT MAKES SENSE. If you prefer strict RFC adherence over
661 * common sense, you can disable this in the configuration.
663 * We also set the "message room name" ('O' field) to MAILROOM
664 * (which is Mail> on most systems) to prevent it from getting set
665 * to something ugly like "0000058008.Sent Items>" when the message
666 * is read with a Citadel client.
668 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
669 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
670 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
671 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
672 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
673 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
674 msg->cm_fields['A'] = strdup(CC->user.fullname);
675 msg->cm_fields['N'] = strdup(config.c_nodename);
676 msg->cm_fields['H'] = strdup(config.c_humannode);
677 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
678 msg->cm_fields['O'] = strdup(MAILROOM);
681 /* Submit the message into the Citadel system. */
682 valid = validate_recipients(SMTP->recipients);
684 /* If there are modules that want to scan this message before final
685 * submission (such as virus checkers or spam filters), call them now
686 * and give them an opportunity to reject the message.
688 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
690 if (scan_errors > 0) { /* We don't want this message! */
692 if (msg->cm_fields['0'] == NULL) {
693 msg->cm_fields['0'] = strdup(
694 "5.7.1 Message rejected by filter");
697 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
700 else { /* Ok, we'll accept this message. */
701 msgnum = CtdlSubmitMsg(msg, valid, "");
703 sprintf(result, "250 2.0.0 Message accepted.\r\n");
706 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
710 /* For SMTP and ESTMP, just print the result message. For LMTP, we
711 * have to print one result message for each recipient. Since there
712 * is nothing in Citadel which would cause different recipients to
713 * have different results, we can get away with just spitting out the
714 * same message once for each recipient.
717 for (i=0; i<SMTP->number_of_recipients; ++i) {
718 cprintf("%s", result);
722 cprintf("%s", result);
725 CtdlFreeMessage(msg);
727 smtp_data_clear(); /* clear out the buffers now */
732 * implements the STARTTLS command (Citadel API version)
735 void smtp_starttls(void)
737 char ok_response[SIZ];
738 char nosup_response[SIZ];
739 char error_response[SIZ];
742 "200 2.0.0 Begin TLS negotiation now\r\n");
743 sprintf(nosup_response,
744 "554 5.7.3 TLS not supported here\r\n");
745 sprintf(error_response,
746 "554 5.7.3 Internal error\r\n");
747 CtdlStartTLS(ok_response, nosup_response, error_response);
755 * Main command loop for SMTP sessions.
757 void smtp_command_loop(void) {
761 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
762 if (client_gets(cmdbuf) < 1) {
763 lprintf(CTDL_CRIT, "SMTP socket is broken. Ending session.\n");
767 lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
768 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
770 if (SMTP->command_state == smtp_user) {
771 smtp_get_user(cmdbuf);
774 else if (SMTP->command_state == smtp_password) {
775 smtp_get_pass(cmdbuf);
778 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
779 smtp_auth(&cmdbuf[5]);
782 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
786 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
787 smtp_expn(&cmdbuf[5]);
790 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
791 smtp_hello(&cmdbuf[5], 0);
794 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
795 smtp_hello(&cmdbuf[5], 1);
798 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
799 smtp_hello(&cmdbuf[5], 2);
802 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
806 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
807 smtp_mail(&cmdbuf[5]);
810 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
811 cprintf("250 NOOP\r\n");
814 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
815 cprintf("221 Goodbye...\r\n");
820 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
821 smtp_rcpt(&cmdbuf[5]);
824 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
828 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
832 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
833 smtp_vrfy(&cmdbuf[5]);
837 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
846 /*****************************************************************************/
847 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
848 /*****************************************************************************/
855 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
858 void smtp_try(const char *key, const char *addr, int *status,
859 char *dsn, size_t n, long msgnum)
866 char user[SIZ], node[SIZ], name[SIZ];
872 size_t blocksize = 0;
875 /* Parse out the host portion of the recipient address */
876 process_rfc822_addr(addr, user, node, name);
878 lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
881 /* Load the message out of the database into a temp file */
883 if (msg_fp == NULL) {
885 snprintf(dsn, n, "Error creating temporary file");
889 CtdlRedirectOutput(msg_fp, -1);
890 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
891 CtdlRedirectOutput(NULL, -1);
892 fseek(msg_fp, 0L, SEEK_END);
893 msg_size = ftell(msg_fp);
897 /* Extract something to send later in the 'MAIL From:' command */
898 strcpy(mailfrom, "");
902 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
903 if (!strncasecmp(buf, "From:", 5)) {
904 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
906 for (i=0; i<strlen(mailfrom); ++i) {
907 if (!isprint(mailfrom[i])) {
908 strcpy(&mailfrom[i], &mailfrom[i+1]);
913 /* Strip out parenthesized names */
916 for (i=0; i<strlen(mailfrom); ++i) {
917 if (mailfrom[i] == '(') lp = i;
918 if (mailfrom[i] == ')') rp = i;
920 if ((lp>0)&&(rp>lp)) {
921 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
924 /* Prefer brokketized names */
927 for (i=0; i<strlen(mailfrom); ++i) {
928 if (mailfrom[i] == '<') lp = i;
929 if (mailfrom[i] == '>') rp = i;
931 if ( (lp>=0) && (rp>lp) ) {
933 strcpy(mailfrom, &mailfrom[lp]);
938 } while (scan_done == 0);
939 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
941 /* Figure out what mail exchanger host we have to connect to */
942 num_mxhosts = getmx(mxhosts, node);
943 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
944 if (num_mxhosts < 1) {
946 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
951 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
952 extract(buf, mxhosts, mx);
953 lprintf(CTDL_DEBUG, "Trying <%s>\n", buf);
954 sock = sock_connect(buf, "25", "tcp");
955 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
956 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
957 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
961 *status = 4; /* dsn is already filled in */
965 /* Process the SMTP greeting from the server */
966 if (ml_sock_gets(sock, buf) < 0) {
968 strcpy(dsn, "Connection broken during SMTP conversation");
971 lprintf(CTDL_DEBUG, "<%s\n", buf);
975 safestrncpy(dsn, &buf[4], 1023);
980 safestrncpy(dsn, &buf[4], 1023);
985 /* At this point we know we are talking to a real SMTP server */
987 /* Do a HELO command */
988 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
989 lprintf(CTDL_DEBUG, ">%s", buf);
990 sock_write(sock, buf, strlen(buf));
991 if (ml_sock_gets(sock, buf) < 0) {
993 strcpy(dsn, "Connection broken during SMTP HELO");
996 lprintf(CTDL_DEBUG, "<%s\n", buf);
1000 safestrncpy(dsn, &buf[4], 1023);
1005 safestrncpy(dsn, &buf[4], 1023);
1011 /* HELO succeeded, now try the MAIL From: command */
1012 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
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 MAIL");
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 /* MAIL succeeded, now try the RCPT To: command */
1036 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
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 RCPT");
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 /* RCPT succeeded, now try the DATA command */
1060 lprintf(CTDL_DEBUG, ">DATA\n");
1061 sock_write(sock, "DATA\r\n", 6);
1062 if (ml_sock_gets(sock, buf) < 0) {
1064 strcpy(dsn, "Connection broken during SMTP DATA");
1067 lprintf(CTDL_DEBUG, "<%s\n", buf);
1068 if (buf[0] != '3') {
1069 if (buf[0] == '4') {
1071 safestrncpy(dsn, &buf[4], 1023);
1076 safestrncpy(dsn, &buf[4], 1023);
1081 /* If we reach this point, the server is expecting data */
1083 while (msg_size > 0) {
1084 blocksize = sizeof(buf);
1085 if (blocksize > msg_size) blocksize = msg_size;
1086 fread(buf, blocksize, 1, msg_fp);
1087 sock_write(sock, buf, blocksize);
1088 msg_size -= blocksize;
1090 if (buf[blocksize-1] != 10) {
1091 lprintf(CTDL_WARNING, "Possible problem: message did not "
1092 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1096 sock_write(sock, ".\r\n", 3);
1097 if (ml_sock_gets(sock, buf) < 0) {
1099 strcpy(dsn, "Connection broken during SMTP message transmit");
1102 lprintf(CTDL_DEBUG, "%s\n", buf);
1103 if (buf[0] != '2') {
1104 if (buf[0] == '4') {
1106 safestrncpy(dsn, &buf[4], 1023);
1111 safestrncpy(dsn, &buf[4], 1023);
1117 safestrncpy(dsn, &buf[4], 1023);
1120 lprintf(CTDL_DEBUG, ">QUIT\n");
1121 sock_write(sock, "QUIT\r\n", 6);
1122 ml_sock_gets(sock, buf);
1123 lprintf(CTDL_DEBUG, "<%s\n", buf);
1124 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1127 bail: if (msg_fp != NULL) fclose(msg_fp);
1135 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1136 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1137 * a "bounce" message (delivery status notification).
1139 void smtp_do_bounce(char *instr) {
1147 char bounceto[1024];
1148 int num_bounces = 0;
1149 int bounce_this = 0;
1150 long bounce_msgid = (-1);
1151 time_t submitted = 0L;
1152 struct CtdlMessage *bmsg = NULL;
1154 struct recptypes *valid;
1155 int successful_bounce = 0;
1157 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1158 strcpy(bounceto, "");
1160 lines = num_tokens(instr, '\n');
1163 /* See if it's time to give up on delivery of this message */
1164 for (i=0; i<lines; ++i) {
1165 extract_token(buf, instr, i, '\n');
1166 extract(key, buf, 0);
1167 extract(addr, buf, 1);
1168 if (!strcasecmp(key, "submitted")) {
1169 submitted = atol(addr);
1173 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1179 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1180 if (bmsg == NULL) return;
1181 memset(bmsg, 0, sizeof(struct CtdlMessage));
1183 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1184 bmsg->cm_anon_type = MES_NORMAL;
1185 bmsg->cm_format_type = 1;
1186 bmsg->cm_fields['A'] = strdup("Citadel");
1187 bmsg->cm_fields['O'] = strdup(MAILROOM);
1188 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1190 if (give_up) bmsg->cm_fields['M'] = strdup(
1191 "A message you sent could not be delivered to some or all of its recipients\n"
1192 "due to prolonged unavailability of its destination(s).\n"
1193 "Giving up on the following addresses:\n\n"
1196 else bmsg->cm_fields['M'] = strdup(
1197 "A message you sent could not be delivered to some or all of its recipients.\n"
1198 "The following addresses were undeliverable:\n\n"
1202 * Now go through the instructions checking for stuff.
1204 for (i=0; i<lines; ++i) {
1205 extract_token(buf, instr, i, '\n');
1206 extract(key, buf, 0);
1207 extract(addr, buf, 1);
1208 status = extract_int(buf, 2);
1209 extract(dsn, buf, 3);
1212 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1213 key, addr, status, dsn);
1215 if (!strcasecmp(key, "bounceto")) {
1216 strcpy(bounceto, addr);
1220 (!strcasecmp(key, "local"))
1221 || (!strcasecmp(key, "remote"))
1222 || (!strcasecmp(key, "ignet"))
1223 || (!strcasecmp(key, "room"))
1225 if (status == 5) bounce_this = 1;
1226 if (give_up) bounce_this = 1;
1232 if (bmsg->cm_fields['M'] == NULL) {
1233 lprintf(CTDL_ERR, "ERROR ... M field is null "
1234 "(%s:%d)\n", __FILE__, __LINE__);
1237 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1238 strlen(bmsg->cm_fields['M']) + 1024 );
1239 strcat(bmsg->cm_fields['M'], addr);
1240 strcat(bmsg->cm_fields['M'], ": ");
1241 strcat(bmsg->cm_fields['M'], dsn);
1242 strcat(bmsg->cm_fields['M'], "\n");
1244 remove_token(instr, i, '\n');
1250 /* Deliver the bounce if there's anything worth mentioning */
1251 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1252 if (num_bounces > 0) {
1254 /* First try the user who sent the message */
1255 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1256 if (strlen(bounceto) == 0) {
1257 lprintf(CTDL_ERR, "No bounce address specified\n");
1258 bounce_msgid = (-1L);
1261 /* Can we deliver the bounce to the original sender? */
1262 valid = validate_recipients(bounceto);
1263 if (valid != NULL) {
1264 if (valid->num_error == 0) {
1265 CtdlSubmitMsg(bmsg, valid, "");
1266 successful_bounce = 1;
1270 /* If not, post it in the Aide> room */
1271 if (successful_bounce == 0) {
1272 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1275 /* Free up the memory we used */
1276 if (valid != NULL) {
1281 CtdlFreeMessage(bmsg);
1282 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1287 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1288 * set of delivery instructions for completed deliveries and remove them.
1290 * It returns the number of incomplete deliveries remaining.
1292 int smtp_purge_completed_deliveries(char *instr) {
1303 lines = num_tokens(instr, '\n');
1304 for (i=0; i<lines; ++i) {
1305 extract_token(buf, instr, i, '\n');
1306 extract(key, buf, 0);
1307 extract(addr, buf, 1);
1308 status = extract_int(buf, 2);
1309 extract(dsn, buf, 3);
1314 (!strcasecmp(key, "local"))
1315 || (!strcasecmp(key, "remote"))
1316 || (!strcasecmp(key, "ignet"))
1317 || (!strcasecmp(key, "room"))
1319 if (status == 2) completed = 1;
1324 remove_token(instr, i, '\n');
1337 * Called by smtp_do_queue() to handle an individual message.
1339 void smtp_do_procmsg(long msgnum, void *userdata) {
1340 struct CtdlMessage *msg;
1342 char *results = NULL;
1350 long text_msgid = (-1);
1351 int incomplete_deliveries_remaining;
1352 time_t attempted = 0L;
1353 time_t last_attempted = 0L;
1354 time_t retry = SMTP_RETRY_INTERVAL;
1356 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1358 msg = CtdlFetchMessage(msgnum, 1);
1360 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1364 instr = strdup(msg->cm_fields['M']);
1365 CtdlFreeMessage(msg);
1367 /* Strip out the headers amd any other non-instruction line */
1368 lines = num_tokens(instr, '\n');
1369 for (i=0; i<lines; ++i) {
1370 extract_token(buf, instr, i, '\n');
1371 if (num_tokens(buf, '|') < 2) {
1372 remove_token(instr, i, '\n');
1378 /* Learn the message ID and find out about recent delivery attempts */
1379 lines = num_tokens(instr, '\n');
1380 for (i=0; i<lines; ++i) {
1381 extract_token(buf, instr, i, '\n');
1382 extract(key, buf, 0);
1383 if (!strcasecmp(key, "msgid")) {
1384 text_msgid = extract_long(buf, 1);
1386 if (!strcasecmp(key, "retry")) {
1387 /* double the retry interval after each attempt */
1388 retry = extract_long(buf, 1) * 2L;
1389 if (retry > SMTP_RETRY_MAX) {
1390 retry = SMTP_RETRY_MAX;
1392 remove_token(instr, i, '\n');
1394 if (!strcasecmp(key, "attempted")) {
1395 attempted = extract_long(buf, 1);
1396 if (attempted > last_attempted)
1397 last_attempted = attempted;
1402 * Postpone delivery if we've already tried recently.
1404 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1405 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1412 * Bail out if there's no actual message associated with this
1414 if (text_msgid < 0L) {
1415 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1420 /* Plow through the instructions looking for 'remote' directives and
1421 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1422 * were experienced and it's time to try again)
1424 lines = num_tokens(instr, '\n');
1425 for (i=0; i<lines; ++i) {
1426 extract_token(buf, instr, i, '\n');
1427 extract(key, buf, 0);
1428 extract(addr, buf, 1);
1429 status = extract_int(buf, 2);
1430 extract(dsn, buf, 3);
1431 if ( (!strcasecmp(key, "remote"))
1432 && ((status==0)||(status==3)||(status==4)) ) {
1434 /* Remove this "remote" instruction from the set,
1435 * but replace the set's final newline if
1436 * remove_token() stripped it. It has to be there.
1438 remove_token(instr, i, '\n');
1439 if (instr[strlen(instr)-1] != '\n') {
1440 strcat(instr, "\n");
1445 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1446 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1448 if (results == NULL) {
1449 results = malloc(1024);
1450 memset(results, 0, 1024);
1453 results = realloc(results,
1454 strlen(results) + 1024);
1456 snprintf(&results[strlen(results)], 1024,
1458 key, addr, status, dsn);
1463 if (results != NULL) {
1464 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1465 strcat(instr, results);
1470 /* Generate 'bounce' messages */
1471 smtp_do_bounce(instr);
1473 /* Go through the delivery list, deleting completed deliveries */
1474 incomplete_deliveries_remaining =
1475 smtp_purge_completed_deliveries(instr);
1479 * No delivery instructions remain, so delete both the instructions
1480 * message and the message message.
1482 if (incomplete_deliveries_remaining <= 0) {
1483 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1484 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1489 * Uncompleted delivery instructions remain, so delete the old
1490 * instructions and replace with the updated ones.
1492 if (incomplete_deliveries_remaining > 0) {
1493 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1494 msg = malloc(sizeof(struct CtdlMessage));
1495 memset(msg, 0, sizeof(struct CtdlMessage));
1496 msg->cm_magic = CTDLMESSAGE_MAGIC;
1497 msg->cm_anon_type = MES_NORMAL;
1498 msg->cm_format_type = FMT_RFC822;
1499 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1500 snprintf(msg->cm_fields['M'],
1502 "Content-type: %s\n\n%s\n"
1505 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1507 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1508 CtdlFreeMessage(msg);
1518 * Run through the queue sending out messages.
1520 void smtp_do_queue(void) {
1521 static int doing_queue = 0;
1524 * This is a simple concurrency check to make sure only one queue run
1525 * is done at a time. We could do this with a mutex, but since we
1526 * don't really require extremely fine granularity here, we'll do it
1527 * with a static variable instead.
1529 if (doing_queue) return;
1533 * Go ahead and run the queue
1535 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1537 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1538 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1541 CtdlForEachMessage(MSGS_ALL, 0L,
1542 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1544 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1551 /*****************************************************************************/
1552 /* SMTP UTILITY COMMANDS */
1553 /*****************************************************************************/
1555 void cmd_smtp(char *argbuf) {
1562 if (CtdlAccessCheck(ac_aide)) return;
1564 extract(cmd, argbuf, 0);
1566 if (!strcasecmp(cmd, "mx")) {
1567 extract(node, argbuf, 1);
1568 num_mxhosts = getmx(buf, node);
1569 cprintf("%d %d MX hosts listed for %s\n",
1570 LISTING_FOLLOWS, num_mxhosts, node);
1571 for (i=0; i<num_mxhosts; ++i) {
1572 extract(node, buf, i);
1573 cprintf("%s\n", node);
1579 else if (!strcasecmp(cmd, "runqueue")) {
1581 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1586 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1593 * Initialize the SMTP outbound queue
1595 void smtp_init_spoolout(void) {
1596 struct ctdlroom qrbuf;
1599 * Create the room. This will silently fail if the room already
1600 * exists, and that's perfectly ok, because we want it to exist.
1602 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0);
1605 * Make sure it's set to be a "system room" so it doesn't show up
1606 * in the <K>nown rooms list for Aides.
1608 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1609 qrbuf.QRflags2 |= QR2_SYSTEM;
1617 /*****************************************************************************/
1618 /* MODULE INITIALIZATION STUFF */
1619 /*****************************************************************************/
1622 char *serv_smtp_init(void)
1624 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1630 CtdlRegisterServiceHook(0, /* ...and locally */
1636 smtp_init_spoolout();
1637 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1638 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");