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 msgtext = CC->redirect_buffer;
923 msg_size = CC->redirect_len;
924 CC->redirect_buffer = NULL;
925 CC->redirect_len = 0;
926 CC->redirect_alloc = 0;
928 /* Extract something to send later in the 'MAIL From:' command */
929 strcpy(mailfrom, "");
933 if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
936 if (!strncasecmp(buf, "From:", 5)) {
937 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
939 for (i=0; i<strlen(mailfrom); ++i) {
940 if (!isprint(mailfrom[i])) {
941 strcpy(&mailfrom[i], &mailfrom[i+1]);
946 /* Strip out parenthesized names */
949 for (i=0; i<strlen(mailfrom); ++i) {
950 if (mailfrom[i] == '(') lp = i;
951 if (mailfrom[i] == ')') rp = i;
953 if ((lp>0)&&(rp>lp)) {
954 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
957 /* Prefer brokketized names */
960 for (i=0; i<strlen(mailfrom); ++i) {
961 if (mailfrom[i] == '<') lp = i;
962 if (mailfrom[i] == '>') rp = i;
964 if ( (lp>=0) && (rp>lp) ) {
966 strcpy(mailfrom, &mailfrom[lp]);
971 } while (scan_done == 0);
972 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
974 /* Figure out what mail exchanger host we have to connect to */
975 num_mxhosts = getmx(mxhosts, node);
976 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
977 if (num_mxhosts < 1) {
979 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
984 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
985 extract(buf, mxhosts, mx);
986 lprintf(CTDL_DEBUG, "Trying <%s>\n", buf);
987 sock = sock_connect(buf, "25", "tcp");
988 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
989 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
990 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
994 *status = 4; /* dsn is already filled in */
998 /* Process the SMTP greeting from the server */
999 if (ml_sock_gets(sock, buf) < 0) {
1001 strcpy(dsn, "Connection broken during SMTP conversation");
1004 lprintf(CTDL_DEBUG, "<%s\n", buf);
1005 if (buf[0] != '2') {
1006 if (buf[0] == '4') {
1008 safestrncpy(dsn, &buf[4], 1023);
1013 safestrncpy(dsn, &buf[4], 1023);
1018 /* At this point we know we are talking to a real SMTP server */
1020 /* Do a HELO command */
1021 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1022 lprintf(CTDL_DEBUG, ">%s", buf);
1023 sock_write(sock, buf, strlen(buf));
1024 if (ml_sock_gets(sock, buf) < 0) {
1026 strcpy(dsn, "Connection broken during SMTP HELO");
1029 lprintf(CTDL_DEBUG, "<%s\n", buf);
1030 if (buf[0] != '2') {
1031 if (buf[0] == '4') {
1033 safestrncpy(dsn, &buf[4], 1023);
1038 safestrncpy(dsn, &buf[4], 1023);
1043 /* HELO succeeded, now try the MAIL From: command */
1044 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1045 lprintf(CTDL_DEBUG, ">%s", buf);
1046 sock_write(sock, buf, strlen(buf));
1047 if (ml_sock_gets(sock, buf) < 0) {
1049 strcpy(dsn, "Connection broken during SMTP MAIL");
1052 lprintf(CTDL_DEBUG, "<%s\n", buf);
1053 if (buf[0] != '2') {
1054 if (buf[0] == '4') {
1056 safestrncpy(dsn, &buf[4], 1023);
1061 safestrncpy(dsn, &buf[4], 1023);
1066 /* MAIL succeeded, now try the RCPT To: command */
1067 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
1068 lprintf(CTDL_DEBUG, ">%s", buf);
1069 sock_write(sock, buf, strlen(buf));
1070 if (ml_sock_gets(sock, buf) < 0) {
1072 strcpy(dsn, "Connection broken during SMTP RCPT");
1075 lprintf(CTDL_DEBUG, "<%s\n", buf);
1076 if (buf[0] != '2') {
1077 if (buf[0] == '4') {
1079 safestrncpy(dsn, &buf[4], 1023);
1084 safestrncpy(dsn, &buf[4], 1023);
1089 /* RCPT succeeded, now try the DATA command */
1090 lprintf(CTDL_DEBUG, ">DATA\n");
1091 sock_write(sock, "DATA\r\n", 6);
1092 if (ml_sock_gets(sock, buf) < 0) {
1094 strcpy(dsn, "Connection broken during SMTP DATA");
1097 lprintf(CTDL_DEBUG, "<%s\n", buf);
1098 if (buf[0] != '3') {
1099 if (buf[0] == '4') {
1101 safestrncpy(dsn, &buf[4], 1023);
1106 safestrncpy(dsn, &buf[4], 1023);
1111 /* If we reach this point, the server is expecting data */
1112 sock_write(sock, msgtext, msg_size);
1113 if (msgtext[msg_size-1] != 10) {
1114 lprintf(CTDL_WARNING, "Possible problem: message did not "
1115 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1119 sock_write(sock, ".\r\n", 3);
1120 if (ml_sock_gets(sock, buf) < 0) {
1122 strcpy(dsn, "Connection broken during SMTP message transmit");
1125 lprintf(CTDL_DEBUG, "%s\n", buf);
1126 if (buf[0] != '2') {
1127 if (buf[0] == '4') {
1129 safestrncpy(dsn, &buf[4], 1023);
1134 safestrncpy(dsn, &buf[4], 1023);
1140 safestrncpy(dsn, &buf[4], 1023);
1143 lprintf(CTDL_DEBUG, ">QUIT\n");
1144 sock_write(sock, "QUIT\r\n", 6);
1145 ml_sock_gets(sock, buf);
1146 lprintf(CTDL_DEBUG, "<%s\n", buf);
1147 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1150 bail: free(msgtext);
1158 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1159 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1160 * a "bounce" message (delivery status notification).
1162 void smtp_do_bounce(char *instr) {
1170 char bounceto[1024];
1171 int num_bounces = 0;
1172 int bounce_this = 0;
1173 long bounce_msgid = (-1);
1174 time_t submitted = 0L;
1175 struct CtdlMessage *bmsg = NULL;
1177 struct recptypes *valid;
1178 int successful_bounce = 0;
1180 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1181 strcpy(bounceto, "");
1183 lines = num_tokens(instr, '\n');
1186 /* See if it's time to give up on delivery of this message */
1187 for (i=0; i<lines; ++i) {
1188 extract_token(buf, instr, i, '\n');
1189 extract(key, buf, 0);
1190 extract(addr, buf, 1);
1191 if (!strcasecmp(key, "submitted")) {
1192 submitted = atol(addr);
1196 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1202 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1203 if (bmsg == NULL) return;
1204 memset(bmsg, 0, sizeof(struct CtdlMessage));
1206 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1207 bmsg->cm_anon_type = MES_NORMAL;
1208 bmsg->cm_format_type = 1;
1209 bmsg->cm_fields['A'] = strdup("Citadel");
1210 bmsg->cm_fields['O'] = strdup(MAILROOM);
1211 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1213 if (give_up) bmsg->cm_fields['M'] = strdup(
1214 "A message you sent could not be delivered to some or all of its recipients\n"
1215 "due to prolonged unavailability of its destination(s).\n"
1216 "Giving up on the following addresses:\n\n"
1219 else bmsg->cm_fields['M'] = strdup(
1220 "A message you sent could not be delivered to some or all of its recipients.\n"
1221 "The following addresses were undeliverable:\n\n"
1225 * Now go through the instructions checking for stuff.
1227 for (i=0; i<lines; ++i) {
1228 extract_token(buf, instr, i, '\n');
1229 extract(key, buf, 0);
1230 extract(addr, buf, 1);
1231 status = extract_int(buf, 2);
1232 extract(dsn, buf, 3);
1235 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1236 key, addr, status, dsn);
1238 if (!strcasecmp(key, "bounceto")) {
1239 strcpy(bounceto, addr);
1243 (!strcasecmp(key, "local"))
1244 || (!strcasecmp(key, "remote"))
1245 || (!strcasecmp(key, "ignet"))
1246 || (!strcasecmp(key, "room"))
1248 if (status == 5) bounce_this = 1;
1249 if (give_up) bounce_this = 1;
1255 if (bmsg->cm_fields['M'] == NULL) {
1256 lprintf(CTDL_ERR, "ERROR ... M field is null "
1257 "(%s:%d)\n", __FILE__, __LINE__);
1260 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1261 strlen(bmsg->cm_fields['M']) + 1024 );
1262 strcat(bmsg->cm_fields['M'], addr);
1263 strcat(bmsg->cm_fields['M'], ": ");
1264 strcat(bmsg->cm_fields['M'], dsn);
1265 strcat(bmsg->cm_fields['M'], "\n");
1267 remove_token(instr, i, '\n');
1273 /* Deliver the bounce if there's anything worth mentioning */
1274 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1275 if (num_bounces > 0) {
1277 /* First try the user who sent the message */
1278 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1279 if (strlen(bounceto) == 0) {
1280 lprintf(CTDL_ERR, "No bounce address specified\n");
1281 bounce_msgid = (-1L);
1284 /* Can we deliver the bounce to the original sender? */
1285 valid = validate_recipients(bounceto);
1286 if (valid != NULL) {
1287 if (valid->num_error == 0) {
1288 CtdlSubmitMsg(bmsg, valid, "");
1289 successful_bounce = 1;
1293 /* If not, post it in the Aide> room */
1294 if (successful_bounce == 0) {
1295 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1298 /* Free up the memory we used */
1299 if (valid != NULL) {
1304 CtdlFreeMessage(bmsg);
1305 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1310 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1311 * set of delivery instructions for completed deliveries and remove them.
1313 * It returns the number of incomplete deliveries remaining.
1315 int smtp_purge_completed_deliveries(char *instr) {
1326 lines = num_tokens(instr, '\n');
1327 for (i=0; i<lines; ++i) {
1328 extract_token(buf, instr, i, '\n');
1329 extract(key, buf, 0);
1330 extract(addr, buf, 1);
1331 status = extract_int(buf, 2);
1332 extract(dsn, buf, 3);
1337 (!strcasecmp(key, "local"))
1338 || (!strcasecmp(key, "remote"))
1339 || (!strcasecmp(key, "ignet"))
1340 || (!strcasecmp(key, "room"))
1342 if (status == 2) completed = 1;
1347 remove_token(instr, i, '\n');
1360 * Called by smtp_do_queue() to handle an individual message.
1362 void smtp_do_procmsg(long msgnum, void *userdata) {
1363 struct CtdlMessage *msg;
1365 char *results = NULL;
1373 long text_msgid = (-1);
1374 int incomplete_deliveries_remaining;
1375 time_t attempted = 0L;
1376 time_t last_attempted = 0L;
1377 time_t retry = SMTP_RETRY_INTERVAL;
1379 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1381 msg = CtdlFetchMessage(msgnum, 1);
1383 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1387 instr = strdup(msg->cm_fields['M']);
1388 CtdlFreeMessage(msg);
1390 /* Strip out the headers amd any other non-instruction line */
1391 lines = num_tokens(instr, '\n');
1392 for (i=0; i<lines; ++i) {
1393 extract_token(buf, instr, i, '\n');
1394 if (num_tokens(buf, '|') < 2) {
1395 remove_token(instr, i, '\n');
1401 /* Learn the message ID and find out about recent delivery attempts */
1402 lines = num_tokens(instr, '\n');
1403 for (i=0; i<lines; ++i) {
1404 extract_token(buf, instr, i, '\n');
1405 extract(key, buf, 0);
1406 if (!strcasecmp(key, "msgid")) {
1407 text_msgid = extract_long(buf, 1);
1409 if (!strcasecmp(key, "retry")) {
1410 /* double the retry interval after each attempt */
1411 retry = extract_long(buf, 1) * 2L;
1412 if (retry > SMTP_RETRY_MAX) {
1413 retry = SMTP_RETRY_MAX;
1415 remove_token(instr, i, '\n');
1417 if (!strcasecmp(key, "attempted")) {
1418 attempted = extract_long(buf, 1);
1419 if (attempted > last_attempted)
1420 last_attempted = attempted;
1425 * Postpone delivery if we've already tried recently.
1427 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1428 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1435 * Bail out if there's no actual message associated with this
1437 if (text_msgid < 0L) {
1438 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1443 /* Plow through the instructions looking for 'remote' directives and
1444 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1445 * were experienced and it's time to try again)
1447 lines = num_tokens(instr, '\n');
1448 for (i=0; i<lines; ++i) {
1449 extract_token(buf, instr, i, '\n');
1450 extract(key, buf, 0);
1451 extract(addr, buf, 1);
1452 status = extract_int(buf, 2);
1453 extract(dsn, buf, 3);
1454 if ( (!strcasecmp(key, "remote"))
1455 && ((status==0)||(status==3)||(status==4)) ) {
1457 /* Remove this "remote" instruction from the set,
1458 * but replace the set's final newline if
1459 * remove_token() stripped it. It has to be there.
1461 remove_token(instr, i, '\n');
1462 if (instr[strlen(instr)-1] != '\n') {
1463 strcat(instr, "\n");
1468 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1469 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1471 if (results == NULL) {
1472 results = malloc(1024);
1473 memset(results, 0, 1024);
1476 results = realloc(results,
1477 strlen(results) + 1024);
1479 snprintf(&results[strlen(results)], 1024,
1481 key, addr, status, dsn);
1486 if (results != NULL) {
1487 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1488 strcat(instr, results);
1493 /* Generate 'bounce' messages */
1494 smtp_do_bounce(instr);
1496 /* Go through the delivery list, deleting completed deliveries */
1497 incomplete_deliveries_remaining =
1498 smtp_purge_completed_deliveries(instr);
1502 * No delivery instructions remain, so delete both the instructions
1503 * message and the message message.
1505 if (incomplete_deliveries_remaining <= 0) {
1506 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1507 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1512 * Uncompleted delivery instructions remain, so delete the old
1513 * instructions and replace with the updated ones.
1515 if (incomplete_deliveries_remaining > 0) {
1516 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1517 msg = malloc(sizeof(struct CtdlMessage));
1518 memset(msg, 0, sizeof(struct CtdlMessage));
1519 msg->cm_magic = CTDLMESSAGE_MAGIC;
1520 msg->cm_anon_type = MES_NORMAL;
1521 msg->cm_format_type = FMT_RFC822;
1522 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1523 snprintf(msg->cm_fields['M'],
1525 "Content-type: %s\n\n%s\n"
1528 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1529 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1530 CtdlFreeMessage(msg);
1541 * Run through the queue sending out messages.
1543 void smtp_do_queue(void) {
1544 static int doing_queue = 0;
1547 * This is a simple concurrency check to make sure only one queue run
1548 * is done at a time. We could do this with a mutex, but since we
1549 * don't really require extremely fine granularity here, we'll do it
1550 * with a static variable instead.
1552 if (doing_queue) return;
1556 * Go ahead and run the queue
1558 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1560 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1561 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1564 CtdlForEachMessage(MSGS_ALL, 0L,
1565 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1567 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1574 /*****************************************************************************/
1575 /* SMTP UTILITY COMMANDS */
1576 /*****************************************************************************/
1578 void cmd_smtp(char *argbuf) {
1585 if (CtdlAccessCheck(ac_aide)) return;
1587 extract(cmd, argbuf, 0);
1589 if (!strcasecmp(cmd, "mx")) {
1590 extract(node, argbuf, 1);
1591 num_mxhosts = getmx(buf, node);
1592 cprintf("%d %d MX hosts listed for %s\n",
1593 LISTING_FOLLOWS, num_mxhosts, node);
1594 for (i=0; i<num_mxhosts; ++i) {
1595 extract(node, buf, i);
1596 cprintf("%s\n", node);
1602 else if (!strcasecmp(cmd, "runqueue")) {
1604 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1609 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1616 * Initialize the SMTP outbound queue
1618 void smtp_init_spoolout(void) {
1619 struct ctdlroom qrbuf;
1622 * Create the room. This will silently fail if the room already
1623 * exists, and that's perfectly ok, because we want it to exist.
1625 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1628 * Make sure it's set to be a "system room" so it doesn't show up
1629 * in the <K>nown rooms list for Aides.
1631 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1632 qrbuf.QRflags2 |= QR2_SYSTEM;
1640 /*****************************************************************************/
1641 /* MODULE INITIALIZATION STUFF */
1642 /*****************************************************************************/
1645 char *serv_smtp_init(void)
1647 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1654 CtdlRegisterServiceHook(config.c_smtps_port,
1661 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1667 CtdlRegisterServiceHook(0, /* local LMTP */
1673 smtp_init_spoolout();
1674 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1675 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");