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 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);
203 /* Only offer the PIPELINING command if TLS is inactive, because
204 * of flow control issues. Also, avoid offering TLS if TLS is
207 if (!CC->redirect_ssl) {
208 cprintf("250-PIPELINING\r\n");
209 cprintf("250-STARTTLS\r\n");
212 #else /* HAVE_OPENSSL */
214 /* Non SSL enabled server, so always offer PIPELINING. */
215 cprintf("250-PIPELINING\r\n");
217 #endif /* HAVE_OPENSSL */
219 cprintf("250-AUTH LOGIN PLAIN\r\n");
220 cprintf("250-AUTH=LOGIN PLAIN\r\n");
222 cprintf("250 ENHANCEDSTATUSCODES\r\n");
229 * Implement HELP command.
231 void smtp_help(void) {
232 cprintf("214-Commands accepted:\r\n");
233 cprintf("214- DATA\r\n");
234 cprintf("214- EHLO\r\n");
235 cprintf("214- EXPN\r\n");
236 cprintf("214- HELO\r\n");
237 cprintf("214- HELP\r\n");
238 cprintf("214- MAIL\r\n");
239 cprintf("214- NOOP\r\n");
240 cprintf("214- QUIT\r\n");
241 cprintf("214- RCPT\r\n");
242 cprintf("214- RSET\r\n");
243 cprintf("214- VRFY\r\n");
251 void smtp_get_user(char *argbuf) {
255 CtdlDecodeBase64(username, argbuf, SIZ);
256 lprintf(CTDL_DEBUG, "Trying <%s>\n", username);
257 if (CtdlLoginExistingUser(username) == login_ok) {
258 CtdlEncodeBase64(buf, "Password:", 9);
259 cprintf("334 %s\r\n", buf);
260 SMTP->command_state = smtp_password;
263 cprintf("500 5.7.0 No such user.\r\n");
264 SMTP->command_state = smtp_command;
272 void smtp_get_pass(char *argbuf) {
275 CtdlDecodeBase64(password, argbuf, SIZ);
276 lprintf(CTDL_DEBUG, "Trying <%s>\n", password);
277 if (CtdlTryPassword(password) == pass_ok) {
278 smtp_auth_greeting();
281 cprintf("535 5.7.0 Authentication failed.\r\n");
283 SMTP->command_state = smtp_command;
290 void smtp_auth(char *argbuf) {
293 char encoded_authstring[SIZ];
294 char decoded_authstring[SIZ];
300 cprintf("504 5.7.4 Already logged in.\r\n");
304 extract_token(method, argbuf, 0, ' ');
306 if (!strncasecmp(method, "login", 5) ) {
307 if (strlen(argbuf) >= 7) {
308 smtp_get_user(&argbuf[6]);
311 CtdlEncodeBase64(buf, "Username:", 9);
312 cprintf("334 %s\r\n", buf);
313 SMTP->command_state = smtp_user;
318 if (!strncasecmp(method, "plain", 5) ) {
319 extract_token(encoded_authstring, argbuf, 1, ' ');
320 CtdlDecodeBase64(decoded_authstring,
322 strlen(encoded_authstring) );
323 strcpy(ident, decoded_authstring);
324 strcpy(user, &decoded_authstring[strlen(ident) + 1] );
325 strcpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2] );
327 if (CtdlLoginExistingUser(user) == login_ok) {
328 if (CtdlTryPassword(pass) == pass_ok) {
329 smtp_auth_greeting();
333 cprintf("504 5.7.4 Authentication failed.\r\n");
336 if (strncasecmp(method, "login", 5) ) {
337 cprintf("504 5.7.4 Unknown authentication method.\r\n");
345 * Back end for smtp_vrfy() command
347 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
349 if (!fuzzy_match(us, SMTP->vrfy_match)) {
351 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
357 * Implements the VRFY (verify user name) command.
358 * Performs fuzzy match on full user names.
360 void smtp_vrfy(char *argbuf) {
361 SMTP->vrfy_count = 0;
362 strcpy(SMTP->vrfy_match, argbuf);
363 ForEachUser(smtp_vrfy_backend, NULL);
365 if (SMTP->vrfy_count < 1) {
366 cprintf("550 5.1.1 String does not match anything.\r\n");
368 else if (SMTP->vrfy_count == 1) {
369 cprintf("250 %s <cit%ld@%s>\r\n",
370 SMTP->vrfy_buffer.fullname,
371 SMTP->vrfy_buffer.usernum,
374 else if (SMTP->vrfy_count > 1) {
375 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
384 * Back end for smtp_expn() command
386 void smtp_expn_backend(struct ctdluser *us, void *data) {
388 if (!fuzzy_match(us, SMTP->vrfy_match)) {
390 if (SMTP->vrfy_count >= 1) {
391 cprintf("250-%s <cit%ld@%s>\r\n",
392 SMTP->vrfy_buffer.fullname,
393 SMTP->vrfy_buffer.usernum,
398 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
404 * Implements the EXPN (expand user name) command.
405 * Performs fuzzy match on full user names.
407 void smtp_expn(char *argbuf) {
408 SMTP->vrfy_count = 0;
409 strcpy(SMTP->vrfy_match, argbuf);
410 ForEachUser(smtp_expn_backend, NULL);
412 if (SMTP->vrfy_count < 1) {
413 cprintf("550 5.1.1 String does not match anything.\r\n");
415 else if (SMTP->vrfy_count >= 1) {
416 cprintf("250 %s <cit%ld@%s>\r\n",
417 SMTP->vrfy_buffer.fullname,
418 SMTP->vrfy_buffer.usernum,
425 * Implements the RSET (reset state) command.
426 * Currently this just zeroes out the state buffer. If pointers to data
427 * allocated with malloc() are ever placed in the state buffer, we have to
428 * be sure to free() them first!
430 * Set do_response to nonzero to output the SMTP RSET response code.
432 void smtp_rset(int do_response) {
436 * Our entire SMTP state is discarded when a RSET command is issued,
437 * but we need to preserve this one little piece of information, so
438 * we save it for later.
440 is_lmtp = SMTP->is_lmtp;
442 memset(SMTP, 0, sizeof(struct citsmtp));
445 * It is somewhat ambiguous whether we want to log out when a RSET
446 * command is issued. Here's the code to do it. It is commented out
447 * because some clients (such as Pine) issue RSET commands before
448 * each message, but still expect to be logged in.
450 * if (CC->logged_in) {
456 * Reinstate this little piece of information we saved (see above).
458 SMTP->is_lmtp = is_lmtp;
461 cprintf("250 2.0.0 Zap!\r\n");
466 * Clear out the portions of the state buffer that need to be cleared out
467 * after the DATA command finishes.
469 void smtp_data_clear(void) {
470 strcpy(SMTP->from, "");
471 strcpy(SMTP->recipients, "");
472 SMTP->number_of_recipients = 0;
473 SMTP->delivery_mode = 0;
474 SMTP->message_originated_locally = 0;
480 * Implements the "MAIL From:" command
482 void smtp_mail(char *argbuf) {
487 if (strlen(SMTP->from) != 0) {
488 cprintf("503 5.1.0 Only one sender permitted\r\n");
492 if (strncasecmp(argbuf, "From:", 5)) {
493 cprintf("501 5.1.7 Syntax error\r\n");
497 strcpy(SMTP->from, &argbuf[5]);
499 stripallbut(SMTP->from, '<', '>');
501 /* We used to reject empty sender names, until it was brought to our
502 * attention that RFC1123 5.2.9 requires that this be allowed. So now
503 * we allow it, but replace the empty string with a fake
504 * address so we don't have to contend with the empty string causing
505 * other code to fail when it's expecting something there.
507 if (strlen(SMTP->from) == 0) {
508 strcpy(SMTP->from, "someone@somewhere.org");
511 /* If this SMTP connection is from a logged-in user, force the 'from'
512 * to be the user's Internet e-mail address as Citadel knows it.
515 strcpy(SMTP->from, CC->cs_inet_email);
516 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
517 SMTP->message_originated_locally = 1;
521 else if (SMTP->is_lmtp) {
522 /* Bypass forgery checking for LMTP */
525 /* Otherwise, make sure outsiders aren't trying to forge mail from
529 process_rfc822_addr(SMTP->from, user, node, name);
530 if (CtdlHostAlias(node) != hostalias_nomatch) {
532 "You must log in to send mail from %s\r\n",
534 strcpy(SMTP->from, "");
539 cprintf("250 2.0.0 Sender ok\r\n");
545 * Implements the "RCPT To:" command
547 void smtp_rcpt(char *argbuf) {
549 char message_to_spammer[SIZ];
550 struct recptypes *valid = NULL;
552 if (strlen(SMTP->from) == 0) {
553 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
557 if (strncasecmp(argbuf, "To:", 3)) {
558 cprintf("501 5.1.7 Syntax error\r\n");
562 strcpy(recp, &argbuf[3]);
564 stripallbut(recp, '<', '>');
566 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
567 cprintf("452 4.5.3 Too many recipients\r\n");
572 if ( (!CC->logged_in)
573 && (!SMTP->is_lmtp) ) {
574 if (rbl_check(message_to_spammer)) {
575 cprintf("550 %s\r\n", message_to_spammer);
576 /* no need to free(valid), it's not allocated yet */
581 valid = validate_recipients(recp);
582 if (valid->num_error > 0) {
583 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
588 if (valid->num_internet > 0) {
590 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
591 cprintf("551 5.7.1 <%s> - you do not have permission to send Internet mail\r\n", recp);
598 if (valid->num_internet > 0) {
599 if ( (SMTP->message_originated_locally == 0)
600 && (SMTP->is_lmtp == 0) ) {
601 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
607 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
608 if (strlen(SMTP->recipients) > 0) {
609 strcat(SMTP->recipients, ",");
611 strcat(SMTP->recipients, recp);
612 SMTP->number_of_recipients += 1;
619 * Implements the DATA command
621 void smtp_data(void) {
623 struct CtdlMessage *msg;
626 struct recptypes *valid;
631 if (strlen(SMTP->from) == 0) {
632 cprintf("503 5.5.1 Need MAIL command first.\r\n");
636 if (SMTP->number_of_recipients < 1) {
637 cprintf("503 5.5.1 Need RCPT command first.\r\n");
641 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
643 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
646 if (body != NULL) snprintf(body, 4096,
647 "Received: from %s (%s [%s])\n"
655 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
658 "Unable to save message: internal error.\r\n");
662 lprintf(CTDL_DEBUG, "Converting message...\n");
663 msg = convert_internet_message(body);
665 /* If the user is locally authenticated, FORCE the From: header to
666 * show up as the real sender. Yes, this violates the RFC standard,
667 * but IT MAKES SENSE. If you prefer strict RFC adherence over
668 * common sense, you can disable this in the configuration.
670 * We also set the "message room name" ('O' field) to MAILROOM
671 * (which is Mail> on most systems) to prevent it from getting set
672 * to something ugly like "0000058008.Sent Items>" when the message
673 * is read with a Citadel client.
675 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
676 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
677 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
678 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
679 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
680 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
681 msg->cm_fields['A'] = strdup(CC->user.fullname);
682 msg->cm_fields['N'] = strdup(config.c_nodename);
683 msg->cm_fields['H'] = strdup(config.c_humannode);
684 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
685 msg->cm_fields['O'] = strdup(MAILROOM);
688 /* Submit the message into the Citadel system. */
689 valid = validate_recipients(SMTP->recipients);
691 /* If there are modules that want to scan this message before final
692 * submission (such as virus checkers or spam filters), call them now
693 * and give them an opportunity to reject the message.
695 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
697 if (scan_errors > 0) { /* We don't want this message! */
699 if (msg->cm_fields['0'] == NULL) {
700 msg->cm_fields['0'] = strdup(
701 "5.7.1 Message rejected by filter");
704 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
707 else { /* Ok, we'll accept this message. */
708 msgnum = CtdlSubmitMsg(msg, valid, "");
710 sprintf(result, "250 2.0.0 Message accepted.\r\n");
713 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
717 /* For SMTP and ESTMP, just print the result message. For LMTP, we
718 * have to print one result message for each recipient. Since there
719 * is nothing in Citadel which would cause different recipients to
720 * have different results, we can get away with just spitting out the
721 * same message once for each recipient.
724 for (i=0; i<SMTP->number_of_recipients; ++i) {
725 cprintf("%s", result);
729 cprintf("%s", result);
732 CtdlFreeMessage(msg);
734 smtp_data_clear(); /* clear out the buffers now */
739 * implements the STARTTLS command (Citadel API version)
742 void smtp_starttls(void)
744 char ok_response[SIZ];
745 char nosup_response[SIZ];
746 char error_response[SIZ];
749 "200 2.0.0 Begin TLS negotiation now\r\n");
750 sprintf(nosup_response,
751 "554 5.7.3 TLS not supported here\r\n");
752 sprintf(error_response,
753 "554 5.7.3 Internal error\r\n");
754 CtdlStartTLS(ok_response, nosup_response, error_response);
762 * Main command loop for SMTP sessions.
764 void smtp_command_loop(void) {
768 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
769 if (client_gets(cmdbuf) < 1) {
770 lprintf(CTDL_CRIT, "SMTP socket is broken. Ending session.\n");
774 lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
775 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
777 if (SMTP->command_state == smtp_user) {
778 smtp_get_user(cmdbuf);
781 else if (SMTP->command_state == smtp_password) {
782 smtp_get_pass(cmdbuf);
785 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
786 smtp_auth(&cmdbuf[5]);
789 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
793 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
794 smtp_expn(&cmdbuf[5]);
797 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
798 smtp_hello(&cmdbuf[5], 0);
801 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
802 smtp_hello(&cmdbuf[5], 1);
805 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
806 smtp_hello(&cmdbuf[5], 2);
809 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
813 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
814 smtp_mail(&cmdbuf[5]);
817 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
818 cprintf("250 NOOP\r\n");
821 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
822 cprintf("221 Goodbye...\r\n");
827 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
828 smtp_rcpt(&cmdbuf[5]);
831 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
835 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
839 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
840 smtp_vrfy(&cmdbuf[5]);
844 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
853 /*****************************************************************************/
854 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
855 /*****************************************************************************/
862 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
865 void smtp_try(const char *key, const char *addr, int *status,
866 char *dsn, size_t n, long msgnum)
873 char user[SIZ], node[SIZ], name[SIZ];
879 size_t blocksize = 0;
882 /* Parse out the host portion of the recipient address */
883 process_rfc822_addr(addr, user, node, name);
885 lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
888 /* Load the message out of the database into a temp file */
890 if (msg_fp == NULL) {
892 snprintf(dsn, n, "Error creating temporary file");
896 CtdlRedirectOutput(msg_fp, -1);
897 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
898 CtdlRedirectOutput(NULL, -1);
899 fseek(msg_fp, 0L, SEEK_END);
900 msg_size = ftell(msg_fp);
904 /* Extract something to send later in the 'MAIL From:' command */
905 strcpy(mailfrom, "");
909 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
910 if (!strncasecmp(buf, "From:", 5)) {
911 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
913 for (i=0; i<strlen(mailfrom); ++i) {
914 if (!isprint(mailfrom[i])) {
915 strcpy(&mailfrom[i], &mailfrom[i+1]);
920 /* Strip out parenthesized names */
923 for (i=0; i<strlen(mailfrom); ++i) {
924 if (mailfrom[i] == '(') lp = i;
925 if (mailfrom[i] == ')') rp = i;
927 if ((lp>0)&&(rp>lp)) {
928 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
931 /* Prefer brokketized names */
934 for (i=0; i<strlen(mailfrom); ++i) {
935 if (mailfrom[i] == '<') lp = i;
936 if (mailfrom[i] == '>') rp = i;
938 if ( (lp>=0) && (rp>lp) ) {
940 strcpy(mailfrom, &mailfrom[lp]);
945 } while (scan_done == 0);
946 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
948 /* Figure out what mail exchanger host we have to connect to */
949 num_mxhosts = getmx(mxhosts, node);
950 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
951 if (num_mxhosts < 1) {
953 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
958 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
959 extract(buf, mxhosts, mx);
960 lprintf(CTDL_DEBUG, "Trying <%s>\n", buf);
961 sock = sock_connect(buf, "25", "tcp");
962 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
963 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
964 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
968 *status = 4; /* dsn is already filled in */
972 /* Process the SMTP greeting from the server */
973 if (ml_sock_gets(sock, buf) < 0) {
975 strcpy(dsn, "Connection broken during SMTP conversation");
978 lprintf(CTDL_DEBUG, "<%s\n", buf);
982 safestrncpy(dsn, &buf[4], 1023);
987 safestrncpy(dsn, &buf[4], 1023);
992 /* At this point we know we are talking to a real SMTP server */
994 /* Do a HELO command */
995 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
996 lprintf(CTDL_DEBUG, ">%s", buf);
997 sock_write(sock, buf, strlen(buf));
998 if (ml_sock_gets(sock, buf) < 0) {
1000 strcpy(dsn, "Connection broken during SMTP HELO");
1003 lprintf(CTDL_DEBUG, "<%s\n", buf);
1004 if (buf[0] != '2') {
1005 if (buf[0] == '4') {
1007 safestrncpy(dsn, &buf[4], 1023);
1012 safestrncpy(dsn, &buf[4], 1023);
1018 /* HELO succeeded, now try the MAIL From: command */
1019 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1020 lprintf(CTDL_DEBUG, ">%s", buf);
1021 sock_write(sock, buf, strlen(buf));
1022 if (ml_sock_gets(sock, buf) < 0) {
1024 strcpy(dsn, "Connection broken during SMTP MAIL");
1027 lprintf(CTDL_DEBUG, "<%s\n", buf);
1028 if (buf[0] != '2') {
1029 if (buf[0] == '4') {
1031 safestrncpy(dsn, &buf[4], 1023);
1036 safestrncpy(dsn, &buf[4], 1023);
1042 /* MAIL succeeded, now try the RCPT To: command */
1043 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
1044 lprintf(CTDL_DEBUG, ">%s", buf);
1045 sock_write(sock, buf, strlen(buf));
1046 if (ml_sock_gets(sock, buf) < 0) {
1048 strcpy(dsn, "Connection broken during SMTP RCPT");
1051 lprintf(CTDL_DEBUG, "<%s\n", buf);
1052 if (buf[0] != '2') {
1053 if (buf[0] == '4') {
1055 safestrncpy(dsn, &buf[4], 1023);
1060 safestrncpy(dsn, &buf[4], 1023);
1066 /* RCPT succeeded, now try the DATA command */
1067 lprintf(CTDL_DEBUG, ">DATA\n");
1068 sock_write(sock, "DATA\r\n", 6);
1069 if (ml_sock_gets(sock, buf) < 0) {
1071 strcpy(dsn, "Connection broken during SMTP DATA");
1074 lprintf(CTDL_DEBUG, "<%s\n", buf);
1075 if (buf[0] != '3') {
1076 if (buf[0] == '4') {
1078 safestrncpy(dsn, &buf[4], 1023);
1083 safestrncpy(dsn, &buf[4], 1023);
1088 /* If we reach this point, the server is expecting data */
1090 while (msg_size > 0) {
1091 blocksize = sizeof(buf);
1092 if (blocksize > msg_size) blocksize = msg_size;
1093 fread(buf, blocksize, 1, msg_fp);
1094 sock_write(sock, buf, blocksize);
1095 msg_size -= blocksize;
1097 if (buf[blocksize-1] != 10) {
1098 lprintf(CTDL_WARNING, "Possible problem: message did not "
1099 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1103 sock_write(sock, ".\r\n", 3);
1104 if (ml_sock_gets(sock, buf) < 0) {
1106 strcpy(dsn, "Connection broken during SMTP message transmit");
1109 lprintf(CTDL_DEBUG, "%s\n", buf);
1110 if (buf[0] != '2') {
1111 if (buf[0] == '4') {
1113 safestrncpy(dsn, &buf[4], 1023);
1118 safestrncpy(dsn, &buf[4], 1023);
1124 safestrncpy(dsn, &buf[4], 1023);
1127 lprintf(CTDL_DEBUG, ">QUIT\n");
1128 sock_write(sock, "QUIT\r\n", 6);
1129 ml_sock_gets(sock, buf);
1130 lprintf(CTDL_DEBUG, "<%s\n", buf);
1131 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1134 bail: if (msg_fp != NULL) fclose(msg_fp);
1142 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1143 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1144 * a "bounce" message (delivery status notification).
1146 void smtp_do_bounce(char *instr) {
1154 char bounceto[1024];
1155 int num_bounces = 0;
1156 int bounce_this = 0;
1157 long bounce_msgid = (-1);
1158 time_t submitted = 0L;
1159 struct CtdlMessage *bmsg = NULL;
1161 struct recptypes *valid;
1162 int successful_bounce = 0;
1164 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1165 strcpy(bounceto, "");
1167 lines = num_tokens(instr, '\n');
1170 /* See if it's time to give up on delivery of this message */
1171 for (i=0; i<lines; ++i) {
1172 extract_token(buf, instr, i, '\n');
1173 extract(key, buf, 0);
1174 extract(addr, buf, 1);
1175 if (!strcasecmp(key, "submitted")) {
1176 submitted = atol(addr);
1180 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1186 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1187 if (bmsg == NULL) return;
1188 memset(bmsg, 0, sizeof(struct CtdlMessage));
1190 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1191 bmsg->cm_anon_type = MES_NORMAL;
1192 bmsg->cm_format_type = 1;
1193 bmsg->cm_fields['A'] = strdup("Citadel");
1194 bmsg->cm_fields['O'] = strdup(MAILROOM);
1195 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1197 if (give_up) bmsg->cm_fields['M'] = strdup(
1198 "A message you sent could not be delivered to some or all of its recipients\n"
1199 "due to prolonged unavailability of its destination(s).\n"
1200 "Giving up on the following addresses:\n\n"
1203 else bmsg->cm_fields['M'] = strdup(
1204 "A message you sent could not be delivered to some or all of its recipients.\n"
1205 "The following addresses were undeliverable:\n\n"
1209 * Now go through the instructions checking for stuff.
1211 for (i=0; i<lines; ++i) {
1212 extract_token(buf, instr, i, '\n');
1213 extract(key, buf, 0);
1214 extract(addr, buf, 1);
1215 status = extract_int(buf, 2);
1216 extract(dsn, buf, 3);
1219 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1220 key, addr, status, dsn);
1222 if (!strcasecmp(key, "bounceto")) {
1223 strcpy(bounceto, addr);
1227 (!strcasecmp(key, "local"))
1228 || (!strcasecmp(key, "remote"))
1229 || (!strcasecmp(key, "ignet"))
1230 || (!strcasecmp(key, "room"))
1232 if (status == 5) bounce_this = 1;
1233 if (give_up) bounce_this = 1;
1239 if (bmsg->cm_fields['M'] == NULL) {
1240 lprintf(CTDL_ERR, "ERROR ... M field is null "
1241 "(%s:%d)\n", __FILE__, __LINE__);
1244 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1245 strlen(bmsg->cm_fields['M']) + 1024 );
1246 strcat(bmsg->cm_fields['M'], addr);
1247 strcat(bmsg->cm_fields['M'], ": ");
1248 strcat(bmsg->cm_fields['M'], dsn);
1249 strcat(bmsg->cm_fields['M'], "\n");
1251 remove_token(instr, i, '\n');
1257 /* Deliver the bounce if there's anything worth mentioning */
1258 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1259 if (num_bounces > 0) {
1261 /* First try the user who sent the message */
1262 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1263 if (strlen(bounceto) == 0) {
1264 lprintf(CTDL_ERR, "No bounce address specified\n");
1265 bounce_msgid = (-1L);
1268 /* Can we deliver the bounce to the original sender? */
1269 valid = validate_recipients(bounceto);
1270 if (valid != NULL) {
1271 if (valid->num_error == 0) {
1272 CtdlSubmitMsg(bmsg, valid, "");
1273 successful_bounce = 1;
1277 /* If not, post it in the Aide> room */
1278 if (successful_bounce == 0) {
1279 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1282 /* Free up the memory we used */
1283 if (valid != NULL) {
1288 CtdlFreeMessage(bmsg);
1289 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1294 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1295 * set of delivery instructions for completed deliveries and remove them.
1297 * It returns the number of incomplete deliveries remaining.
1299 int smtp_purge_completed_deliveries(char *instr) {
1310 lines = num_tokens(instr, '\n');
1311 for (i=0; i<lines; ++i) {
1312 extract_token(buf, instr, i, '\n');
1313 extract(key, buf, 0);
1314 extract(addr, buf, 1);
1315 status = extract_int(buf, 2);
1316 extract(dsn, buf, 3);
1321 (!strcasecmp(key, "local"))
1322 || (!strcasecmp(key, "remote"))
1323 || (!strcasecmp(key, "ignet"))
1324 || (!strcasecmp(key, "room"))
1326 if (status == 2) completed = 1;
1331 remove_token(instr, i, '\n');
1344 * Called by smtp_do_queue() to handle an individual message.
1346 void smtp_do_procmsg(long msgnum, void *userdata) {
1347 struct CtdlMessage *msg;
1349 char *results = NULL;
1357 long text_msgid = (-1);
1358 int incomplete_deliveries_remaining;
1359 time_t attempted = 0L;
1360 time_t last_attempted = 0L;
1361 time_t retry = SMTP_RETRY_INTERVAL;
1363 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1365 msg = CtdlFetchMessage(msgnum, 1);
1367 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1371 instr = strdup(msg->cm_fields['M']);
1372 CtdlFreeMessage(msg);
1374 /* Strip out the headers amd any other non-instruction line */
1375 lines = num_tokens(instr, '\n');
1376 for (i=0; i<lines; ++i) {
1377 extract_token(buf, instr, i, '\n');
1378 if (num_tokens(buf, '|') < 2) {
1379 remove_token(instr, i, '\n');
1385 /* Learn the message ID and find out about recent delivery attempts */
1386 lines = num_tokens(instr, '\n');
1387 for (i=0; i<lines; ++i) {
1388 extract_token(buf, instr, i, '\n');
1389 extract(key, buf, 0);
1390 if (!strcasecmp(key, "msgid")) {
1391 text_msgid = extract_long(buf, 1);
1393 if (!strcasecmp(key, "retry")) {
1394 /* double the retry interval after each attempt */
1395 retry = extract_long(buf, 1) * 2L;
1396 if (retry > SMTP_RETRY_MAX) {
1397 retry = SMTP_RETRY_MAX;
1399 remove_token(instr, i, '\n');
1401 if (!strcasecmp(key, "attempted")) {
1402 attempted = extract_long(buf, 1);
1403 if (attempted > last_attempted)
1404 last_attempted = attempted;
1409 * Postpone delivery if we've already tried recently.
1411 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1412 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1419 * Bail out if there's no actual message associated with this
1421 if (text_msgid < 0L) {
1422 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1427 /* Plow through the instructions looking for 'remote' directives and
1428 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1429 * were experienced and it's time to try again)
1431 lines = num_tokens(instr, '\n');
1432 for (i=0; i<lines; ++i) {
1433 extract_token(buf, instr, i, '\n');
1434 extract(key, buf, 0);
1435 extract(addr, buf, 1);
1436 status = extract_int(buf, 2);
1437 extract(dsn, buf, 3);
1438 if ( (!strcasecmp(key, "remote"))
1439 && ((status==0)||(status==3)||(status==4)) ) {
1441 /* Remove this "remote" instruction from the set,
1442 * but replace the set's final newline if
1443 * remove_token() stripped it. It has to be there.
1445 remove_token(instr, i, '\n');
1446 if (instr[strlen(instr)-1] != '\n') {
1447 strcat(instr, "\n");
1452 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1453 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1455 if (results == NULL) {
1456 results = malloc(1024);
1457 memset(results, 0, 1024);
1460 results = realloc(results,
1461 strlen(results) + 1024);
1463 snprintf(&results[strlen(results)], 1024,
1465 key, addr, status, dsn);
1470 if (results != NULL) {
1471 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1472 strcat(instr, results);
1477 /* Generate 'bounce' messages */
1478 smtp_do_bounce(instr);
1480 /* Go through the delivery list, deleting completed deliveries */
1481 incomplete_deliveries_remaining =
1482 smtp_purge_completed_deliveries(instr);
1486 * No delivery instructions remain, so delete both the instructions
1487 * message and the message message.
1489 if (incomplete_deliveries_remaining <= 0) {
1490 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1491 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1496 * Uncompleted delivery instructions remain, so delete the old
1497 * instructions and replace with the updated ones.
1499 if (incomplete_deliveries_remaining > 0) {
1500 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1501 msg = malloc(sizeof(struct CtdlMessage));
1502 memset(msg, 0, sizeof(struct CtdlMessage));
1503 msg->cm_magic = CTDLMESSAGE_MAGIC;
1504 msg->cm_anon_type = MES_NORMAL;
1505 msg->cm_format_type = FMT_RFC822;
1506 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1507 snprintf(msg->cm_fields['M'],
1509 "Content-type: %s\n\n%s\n"
1512 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1514 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1515 CtdlFreeMessage(msg);
1525 * Run through the queue sending out messages.
1527 void smtp_do_queue(void) {
1528 static int doing_queue = 0;
1531 * This is a simple concurrency check to make sure only one queue run
1532 * is done at a time. We could do this with a mutex, but since we
1533 * don't really require extremely fine granularity here, we'll do it
1534 * with a static variable instead.
1536 if (doing_queue) return;
1540 * Go ahead and run the queue
1542 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1544 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1545 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1548 CtdlForEachMessage(MSGS_ALL, 0L,
1549 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1551 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1558 /*****************************************************************************/
1559 /* SMTP UTILITY COMMANDS */
1560 /*****************************************************************************/
1562 void cmd_smtp(char *argbuf) {
1569 if (CtdlAccessCheck(ac_aide)) return;
1571 extract(cmd, argbuf, 0);
1573 if (!strcasecmp(cmd, "mx")) {
1574 extract(node, argbuf, 1);
1575 num_mxhosts = getmx(buf, node);
1576 cprintf("%d %d MX hosts listed for %s\n",
1577 LISTING_FOLLOWS, num_mxhosts, node);
1578 for (i=0; i<num_mxhosts; ++i) {
1579 extract(node, buf, i);
1580 cprintf("%s\n", node);
1586 else if (!strcasecmp(cmd, "runqueue")) {
1588 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1593 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1600 * Initialize the SMTP outbound queue
1602 void smtp_init_spoolout(void) {
1603 struct ctdlroom qrbuf;
1606 * Create the room. This will silently fail if the room already
1607 * exists, and that's perfectly ok, because we want it to exist.
1609 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0);
1612 * Make sure it's set to be a "system room" so it doesn't show up
1613 * in the <K>nown rooms list for Aides.
1615 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1616 qrbuf.QRflags2 |= QR2_SYSTEM;
1624 /*****************************************************************************/
1625 /* MODULE INITIALIZATION STUFF */
1626 /*****************************************************************************/
1629 char *serv_smtp_init(void)
1631 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1637 CtdlRegisterServiceHook(0, /* ...and locally */
1643 smtp_init_spoolout();
1644 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1645 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");