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];
908 size_t blocksize = 0;
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 into a temp file */
919 if (msg_fp == NULL) {
921 snprintf(dsn, n, "Error creating temporary file");
925 CtdlRedirectOutput(msg_fp, -1);
926 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
927 CtdlRedirectOutput(NULL, -1);
928 fseek(msg_fp, 0L, SEEK_END);
929 msg_size = ftell(msg_fp);
933 /* Extract something to send later in the 'MAIL From:' command */
934 strcpy(mailfrom, "");
938 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
939 if (!strncasecmp(buf, "From:", 5)) {
940 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
942 for (i=0; i<strlen(mailfrom); ++i) {
943 if (!isprint(mailfrom[i])) {
944 strcpy(&mailfrom[i], &mailfrom[i+1]);
949 /* Strip out parenthesized names */
952 for (i=0; i<strlen(mailfrom); ++i) {
953 if (mailfrom[i] == '(') lp = i;
954 if (mailfrom[i] == ')') rp = i;
956 if ((lp>0)&&(rp>lp)) {
957 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
960 /* Prefer brokketized names */
963 for (i=0; i<strlen(mailfrom); ++i) {
964 if (mailfrom[i] == '<') lp = i;
965 if (mailfrom[i] == '>') rp = i;
967 if ( (lp>=0) && (rp>lp) ) {
969 strcpy(mailfrom, &mailfrom[lp]);
974 } while (scan_done == 0);
975 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
977 /* Figure out what mail exchanger host we have to connect to */
978 num_mxhosts = getmx(mxhosts, node);
979 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
980 if (num_mxhosts < 1) {
982 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
987 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
988 extract(buf, mxhosts, mx);
989 lprintf(CTDL_DEBUG, "Trying <%s>\n", buf);
990 sock = sock_connect(buf, "25", "tcp");
991 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
992 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
993 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
997 *status = 4; /* dsn is already filled in */
1001 /* Process the SMTP greeting from the server */
1002 if (ml_sock_gets(sock, buf) < 0) {
1004 strcpy(dsn, "Connection broken during SMTP conversation");
1007 lprintf(CTDL_DEBUG, "<%s\n", buf);
1008 if (buf[0] != '2') {
1009 if (buf[0] == '4') {
1011 safestrncpy(dsn, &buf[4], 1023);
1016 safestrncpy(dsn, &buf[4], 1023);
1021 /* At this point we know we are talking to a real SMTP server */
1023 /* Do a HELO command */
1024 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1025 lprintf(CTDL_DEBUG, ">%s", buf);
1026 sock_write(sock, buf, strlen(buf));
1027 if (ml_sock_gets(sock, buf) < 0) {
1029 strcpy(dsn, "Connection broken during SMTP HELO");
1032 lprintf(CTDL_DEBUG, "<%s\n", buf);
1033 if (buf[0] != '2') {
1034 if (buf[0] == '4') {
1036 safestrncpy(dsn, &buf[4], 1023);
1041 safestrncpy(dsn, &buf[4], 1023);
1047 /* HELO succeeded, now try the MAIL From: command */
1048 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1049 lprintf(CTDL_DEBUG, ">%s", buf);
1050 sock_write(sock, buf, strlen(buf));
1051 if (ml_sock_gets(sock, buf) < 0) {
1053 strcpy(dsn, "Connection broken during SMTP MAIL");
1056 lprintf(CTDL_DEBUG, "<%s\n", buf);
1057 if (buf[0] != '2') {
1058 if (buf[0] == '4') {
1060 safestrncpy(dsn, &buf[4], 1023);
1065 safestrncpy(dsn, &buf[4], 1023);
1071 /* MAIL succeeded, now try the RCPT To: command */
1072 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
1073 lprintf(CTDL_DEBUG, ">%s", buf);
1074 sock_write(sock, buf, strlen(buf));
1075 if (ml_sock_gets(sock, buf) < 0) {
1077 strcpy(dsn, "Connection broken during SMTP RCPT");
1080 lprintf(CTDL_DEBUG, "<%s\n", buf);
1081 if (buf[0] != '2') {
1082 if (buf[0] == '4') {
1084 safestrncpy(dsn, &buf[4], 1023);
1089 safestrncpy(dsn, &buf[4], 1023);
1095 /* RCPT succeeded, now try the DATA command */
1096 lprintf(CTDL_DEBUG, ">DATA\n");
1097 sock_write(sock, "DATA\r\n", 6);
1098 if (ml_sock_gets(sock, buf) < 0) {
1100 strcpy(dsn, "Connection broken during SMTP DATA");
1103 lprintf(CTDL_DEBUG, "<%s\n", buf);
1104 if (buf[0] != '3') {
1105 if (buf[0] == '4') {
1107 safestrncpy(dsn, &buf[4], 1023);
1112 safestrncpy(dsn, &buf[4], 1023);
1117 /* If we reach this point, the server is expecting data */
1119 while (msg_size > 0) {
1120 blocksize = sizeof(buf);
1121 if (blocksize > msg_size) blocksize = msg_size;
1122 fread(buf, blocksize, 1, msg_fp);
1123 sock_write(sock, buf, blocksize);
1124 msg_size -= blocksize;
1126 if (buf[blocksize-1] != 10) {
1127 lprintf(CTDL_WARNING, "Possible problem: message did not "
1128 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1132 sock_write(sock, ".\r\n", 3);
1133 if (ml_sock_gets(sock, buf) < 0) {
1135 strcpy(dsn, "Connection broken during SMTP message transmit");
1138 lprintf(CTDL_DEBUG, "%s\n", buf);
1139 if (buf[0] != '2') {
1140 if (buf[0] == '4') {
1142 safestrncpy(dsn, &buf[4], 1023);
1147 safestrncpy(dsn, &buf[4], 1023);
1153 safestrncpy(dsn, &buf[4], 1023);
1156 lprintf(CTDL_DEBUG, ">QUIT\n");
1157 sock_write(sock, "QUIT\r\n", 6);
1158 ml_sock_gets(sock, buf);
1159 lprintf(CTDL_DEBUG, "<%s\n", buf);
1160 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1163 bail: if (msg_fp != NULL) fclose(msg_fp);
1171 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1172 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1173 * a "bounce" message (delivery status notification).
1175 void smtp_do_bounce(char *instr) {
1183 char bounceto[1024];
1184 int num_bounces = 0;
1185 int bounce_this = 0;
1186 long bounce_msgid = (-1);
1187 time_t submitted = 0L;
1188 struct CtdlMessage *bmsg = NULL;
1190 struct recptypes *valid;
1191 int successful_bounce = 0;
1193 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1194 strcpy(bounceto, "");
1196 lines = num_tokens(instr, '\n');
1199 /* See if it's time to give up on delivery of this message */
1200 for (i=0; i<lines; ++i) {
1201 extract_token(buf, instr, i, '\n');
1202 extract(key, buf, 0);
1203 extract(addr, buf, 1);
1204 if (!strcasecmp(key, "submitted")) {
1205 submitted = atol(addr);
1209 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1215 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1216 if (bmsg == NULL) return;
1217 memset(bmsg, 0, sizeof(struct CtdlMessage));
1219 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1220 bmsg->cm_anon_type = MES_NORMAL;
1221 bmsg->cm_format_type = 1;
1222 bmsg->cm_fields['A'] = strdup("Citadel");
1223 bmsg->cm_fields['O'] = strdup(MAILROOM);
1224 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1226 if (give_up) bmsg->cm_fields['M'] = strdup(
1227 "A message you sent could not be delivered to some or all of its recipients\n"
1228 "due to prolonged unavailability of its destination(s).\n"
1229 "Giving up on the following addresses:\n\n"
1232 else bmsg->cm_fields['M'] = strdup(
1233 "A message you sent could not be delivered to some or all of its recipients.\n"
1234 "The following addresses were undeliverable:\n\n"
1238 * Now go through the instructions checking for stuff.
1240 for (i=0; i<lines; ++i) {
1241 extract_token(buf, instr, i, '\n');
1242 extract(key, buf, 0);
1243 extract(addr, buf, 1);
1244 status = extract_int(buf, 2);
1245 extract(dsn, buf, 3);
1248 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1249 key, addr, status, dsn);
1251 if (!strcasecmp(key, "bounceto")) {
1252 strcpy(bounceto, addr);
1256 (!strcasecmp(key, "local"))
1257 || (!strcasecmp(key, "remote"))
1258 || (!strcasecmp(key, "ignet"))
1259 || (!strcasecmp(key, "room"))
1261 if (status == 5) bounce_this = 1;
1262 if (give_up) bounce_this = 1;
1268 if (bmsg->cm_fields['M'] == NULL) {
1269 lprintf(CTDL_ERR, "ERROR ... M field is null "
1270 "(%s:%d)\n", __FILE__, __LINE__);
1273 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1274 strlen(bmsg->cm_fields['M']) + 1024 );
1275 strcat(bmsg->cm_fields['M'], addr);
1276 strcat(bmsg->cm_fields['M'], ": ");
1277 strcat(bmsg->cm_fields['M'], dsn);
1278 strcat(bmsg->cm_fields['M'], "\n");
1280 remove_token(instr, i, '\n');
1286 /* Deliver the bounce if there's anything worth mentioning */
1287 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1288 if (num_bounces > 0) {
1290 /* First try the user who sent the message */
1291 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1292 if (strlen(bounceto) == 0) {
1293 lprintf(CTDL_ERR, "No bounce address specified\n");
1294 bounce_msgid = (-1L);
1297 /* Can we deliver the bounce to the original sender? */
1298 valid = validate_recipients(bounceto);
1299 if (valid != NULL) {
1300 if (valid->num_error == 0) {
1301 CtdlSubmitMsg(bmsg, valid, "");
1302 successful_bounce = 1;
1306 /* If not, post it in the Aide> room */
1307 if (successful_bounce == 0) {
1308 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1311 /* Free up the memory we used */
1312 if (valid != NULL) {
1317 CtdlFreeMessage(bmsg);
1318 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1323 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1324 * set of delivery instructions for completed deliveries and remove them.
1326 * It returns the number of incomplete deliveries remaining.
1328 int smtp_purge_completed_deliveries(char *instr) {
1339 lines = num_tokens(instr, '\n');
1340 for (i=0; i<lines; ++i) {
1341 extract_token(buf, instr, i, '\n');
1342 extract(key, buf, 0);
1343 extract(addr, buf, 1);
1344 status = extract_int(buf, 2);
1345 extract(dsn, buf, 3);
1350 (!strcasecmp(key, "local"))
1351 || (!strcasecmp(key, "remote"))
1352 || (!strcasecmp(key, "ignet"))
1353 || (!strcasecmp(key, "room"))
1355 if (status == 2) completed = 1;
1360 remove_token(instr, i, '\n');
1373 * Called by smtp_do_queue() to handle an individual message.
1375 void smtp_do_procmsg(long msgnum, void *userdata) {
1376 struct CtdlMessage *msg;
1378 char *results = NULL;
1386 long text_msgid = (-1);
1387 int incomplete_deliveries_remaining;
1388 time_t attempted = 0L;
1389 time_t last_attempted = 0L;
1390 time_t retry = SMTP_RETRY_INTERVAL;
1392 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1394 msg = CtdlFetchMessage(msgnum, 1);
1396 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1400 instr = strdup(msg->cm_fields['M']);
1401 CtdlFreeMessage(msg);
1403 /* Strip out the headers amd any other non-instruction line */
1404 lines = num_tokens(instr, '\n');
1405 for (i=0; i<lines; ++i) {
1406 extract_token(buf, instr, i, '\n');
1407 if (num_tokens(buf, '|') < 2) {
1408 remove_token(instr, i, '\n');
1414 /* Learn the message ID and find out about recent delivery attempts */
1415 lines = num_tokens(instr, '\n');
1416 for (i=0; i<lines; ++i) {
1417 extract_token(buf, instr, i, '\n');
1418 extract(key, buf, 0);
1419 if (!strcasecmp(key, "msgid")) {
1420 text_msgid = extract_long(buf, 1);
1422 if (!strcasecmp(key, "retry")) {
1423 /* double the retry interval after each attempt */
1424 retry = extract_long(buf, 1) * 2L;
1425 if (retry > SMTP_RETRY_MAX) {
1426 retry = SMTP_RETRY_MAX;
1428 remove_token(instr, i, '\n');
1430 if (!strcasecmp(key, "attempted")) {
1431 attempted = extract_long(buf, 1);
1432 if (attempted > last_attempted)
1433 last_attempted = attempted;
1438 * Postpone delivery if we've already tried recently.
1440 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1441 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1448 * Bail out if there's no actual message associated with this
1450 if (text_msgid < 0L) {
1451 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1456 /* Plow through the instructions looking for 'remote' directives and
1457 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1458 * were experienced and it's time to try again)
1460 lines = num_tokens(instr, '\n');
1461 for (i=0; i<lines; ++i) {
1462 extract_token(buf, instr, i, '\n');
1463 extract(key, buf, 0);
1464 extract(addr, buf, 1);
1465 status = extract_int(buf, 2);
1466 extract(dsn, buf, 3);
1467 if ( (!strcasecmp(key, "remote"))
1468 && ((status==0)||(status==3)||(status==4)) ) {
1470 /* Remove this "remote" instruction from the set,
1471 * but replace the set's final newline if
1472 * remove_token() stripped it. It has to be there.
1474 remove_token(instr, i, '\n');
1475 if (instr[strlen(instr)-1] != '\n') {
1476 strcat(instr, "\n");
1481 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1482 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1484 if (results == NULL) {
1485 results = malloc(1024);
1486 memset(results, 0, 1024);
1489 results = realloc(results,
1490 strlen(results) + 1024);
1492 snprintf(&results[strlen(results)], 1024,
1494 key, addr, status, dsn);
1499 if (results != NULL) {
1500 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1501 strcat(instr, results);
1506 /* Generate 'bounce' messages */
1507 smtp_do_bounce(instr);
1509 /* Go through the delivery list, deleting completed deliveries */
1510 incomplete_deliveries_remaining =
1511 smtp_purge_completed_deliveries(instr);
1515 * No delivery instructions remain, so delete both the instructions
1516 * message and the message message.
1518 if (incomplete_deliveries_remaining <= 0) {
1519 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1520 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1525 * Uncompleted delivery instructions remain, so delete the old
1526 * instructions and replace with the updated ones.
1528 if (incomplete_deliveries_remaining > 0) {
1529 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1530 msg = malloc(sizeof(struct CtdlMessage));
1531 memset(msg, 0, sizeof(struct CtdlMessage));
1532 msg->cm_magic = CTDLMESSAGE_MAGIC;
1533 msg->cm_anon_type = MES_NORMAL;
1534 msg->cm_format_type = FMT_RFC822;
1535 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1536 snprintf(msg->cm_fields['M'],
1538 "Content-type: %s\n\n%s\n"
1541 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1542 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1543 CtdlFreeMessage(msg);
1554 * Run through the queue sending out messages.
1556 void smtp_do_queue(void) {
1557 static int doing_queue = 0;
1560 * This is a simple concurrency check to make sure only one queue run
1561 * is done at a time. We could do this with a mutex, but since we
1562 * don't really require extremely fine granularity here, we'll do it
1563 * with a static variable instead.
1565 if (doing_queue) return;
1569 * Go ahead and run the queue
1571 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1573 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1574 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1577 CtdlForEachMessage(MSGS_ALL, 0L,
1578 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1580 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1587 /*****************************************************************************/
1588 /* SMTP UTILITY COMMANDS */
1589 /*****************************************************************************/
1591 void cmd_smtp(char *argbuf) {
1598 if (CtdlAccessCheck(ac_aide)) return;
1600 extract(cmd, argbuf, 0);
1602 if (!strcasecmp(cmd, "mx")) {
1603 extract(node, argbuf, 1);
1604 num_mxhosts = getmx(buf, node);
1605 cprintf("%d %d MX hosts listed for %s\n",
1606 LISTING_FOLLOWS, num_mxhosts, node);
1607 for (i=0; i<num_mxhosts; ++i) {
1608 extract(node, buf, i);
1609 cprintf("%s\n", node);
1615 else if (!strcasecmp(cmd, "runqueue")) {
1617 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1622 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1629 * Initialize the SMTP outbound queue
1631 void smtp_init_spoolout(void) {
1632 struct ctdlroom qrbuf;
1635 * Create the room. This will silently fail if the room already
1636 * exists, and that's perfectly ok, because we want it to exist.
1638 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_BBS);
1641 * Make sure it's set to be a "system room" so it doesn't show up
1642 * in the <K>nown rooms list for Aides.
1644 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1645 qrbuf.QRflags2 |= QR2_SYSTEM;
1653 /*****************************************************************************/
1654 /* MODULE INITIALIZATION STUFF */
1655 /*****************************************************************************/
1658 char *serv_smtp_init(void)
1660 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1667 CtdlRegisterServiceHook(config.c_smtps_port,
1674 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1680 CtdlRegisterServiceHook(0, /* local LMTP */
1686 smtp_init_spoolout();
1687 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1688 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");