4 * This module is an SMTP and ESMTP implementation for the Citadel system.
5 * It is compliant with all of the following:
7 * RFC 821 - Simple Mail Transfer Protocol
8 * RFC 876 - Survey of SMTP Implementations
9 * RFC 1047 - Duplicate messages and SMTP
10 * RFC 1854 - command pipelining
11 * RFC 1869 - Extended Simple Mail Transfer Protocol
12 * RFC 1870 - SMTP Service Extension for Message Size Declaration
13 * RFC 1893 - Enhanced Mail System Status Codes
14 * RFC 2033 - Local Mail Transfer Protocol
15 * RFC 2034 - SMTP Service Extension for Returning Enhanced Error Codes
16 * RFC 2197 - SMTP Service Extension for Command Pipelining
17 * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
18 * RFC 2554 - SMTP Service Extension for Authentication
19 * RFC 2821 - Simple Mail Transfer Protocol
20 * RFC 2822 - Internet Message Format
21 * RFC 2920 - SMTP Service Extension for Command Pipelining
33 #include <sys/types.h>
35 #if TIME_WITH_SYS_TIME
36 # include <sys/time.h>
40 # include <sys/time.h>
50 #include <sys/socket.h>
51 #include <netinet/in.h>
52 #include <arpa/inet.h>
55 #include "sysdep_decls.h"
56 #include "citserver.h"
60 #include "serv_extensions.h"
67 #include "internet_addressing.h"
70 #include "clientsocket.h"
71 #include "locate_host.h"
74 #include "serv_crypto.h"
83 struct citsmtp { /* Information about the current session */
86 struct ctdluser vrfy_buffer;
91 int number_of_recipients;
93 int message_originated_locally;
97 enum { /* Command states for login authentication */
103 enum { /* Delivery modes */
108 #define SMTP ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
109 #define SMTP_RECPS ((char *)CtdlGetUserData(SYM_SMTP_RECPS))
110 #define SMTP_ROOMS ((char *)CtdlGetUserData(SYM_SMTP_ROOMS))
113 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
117 /*****************************************************************************/
118 /* SMTP SERVER (INBOUND) STUFF */
119 /*****************************************************************************/
125 * Here's where our SMTP session begins its happy day.
127 void smtp_greeting(void) {
129 strcpy(CC->cs_clientname, "SMTP session");
130 CC->internal_pgm = 1;
131 CC->cs_flags |= CS_STEALTH;
132 CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
133 CtdlAllocUserData(SYM_SMTP_RECPS, SIZ);
134 CtdlAllocUserData(SYM_SMTP_ROOMS, SIZ);
135 snprintf(SMTP_RECPS, SIZ, "%s", "");
136 snprintf(SMTP_ROOMS, SIZ, "%s", "");
138 cprintf("220 %s ESMTP Citadel/UX server ready.\r\n", config.c_fqdn);
142 * LMTP is like SMTP but with some extra bonus footage added.
144 void lmtp_greeting(void) {
151 * Login greeting common to all auth methods
153 void smtp_auth_greeting(void) {
154 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
155 lprintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
156 CC->internal_pgm = 0;
157 CC->cs_flags &= ~CS_STEALTH;
162 * Implement HELO and EHLO commands.
164 * which_command: 0=HELO, 1=EHLO, 2=LHLO
166 void smtp_hello(char *argbuf, int which_command) {
168 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
170 if ( (which_command != 2) && (SMTP->is_lmtp) ) {
171 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
175 if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
176 cprintf("500 LHLO is only allowed when running LMTP\r\n");
180 if (which_command == 0) {
181 cprintf("250 Hello %s (%s [%s])\r\n",
188 if (which_command == 1) {
189 cprintf("250-Hello %s (%s [%s])\r\n",
196 cprintf("250-Greetings and joyous salutations.\r\n");
198 cprintf("250-HELP\r\n");
199 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
201 /* Only offer the PIPELINING command if TLS is inactive, because
202 * of flow control issues. Also, avoid offering TLS if TLS is
205 if (!CC->redirect_ssl) {
206 cprintf("250-PIPELINING\r\n");
208 cprintf("250-STARTTLS\r\n");
212 cprintf("250-AUTH LOGIN PLAIN\r\n");
213 cprintf("250-AUTH=LOGIN PLAIN\r\n");
215 cprintf("250 ENHANCEDSTATUSCODES\r\n");
222 * Implement HELP command.
224 void smtp_help(void) {
225 cprintf("214-Commands accepted:\r\n");
226 cprintf("214- DATA\r\n");
227 cprintf("214- EHLO\r\n");
228 cprintf("214- EXPN\r\n");
229 cprintf("214- HELO\r\n");
230 cprintf("214- HELP\r\n");
231 cprintf("214- MAIL\r\n");
232 cprintf("214- NOOP\r\n");
233 cprintf("214- QUIT\r\n");
234 cprintf("214- RCPT\r\n");
235 cprintf("214- RSET\r\n");
236 cprintf("214- VRFY\r\n");
244 void smtp_get_user(char *argbuf) {
248 CtdlDecodeBase64(username, argbuf, SIZ);
249 lprintf(CTDL_DEBUG, "Trying <%s>\n", username);
250 if (CtdlLoginExistingUser(username) == login_ok) {
251 CtdlEncodeBase64(buf, "Password:", 9);
252 cprintf("334 %s\r\n", buf);
253 SMTP->command_state = smtp_password;
256 cprintf("500 5.7.0 No such user.\r\n");
257 SMTP->command_state = smtp_command;
265 void smtp_get_pass(char *argbuf) {
268 CtdlDecodeBase64(password, argbuf, SIZ);
269 lprintf(CTDL_DEBUG, "Trying <%s>\n", password);
270 if (CtdlTryPassword(password) == pass_ok) {
271 smtp_auth_greeting();
274 cprintf("535 5.7.0 Authentication failed.\r\n");
276 SMTP->command_state = smtp_command;
283 void smtp_auth(char *argbuf) {
286 char encoded_authstring[SIZ];
287 char decoded_authstring[SIZ];
293 cprintf("504 5.7.4 Already logged in.\r\n");
297 extract_token(method, argbuf, 0, ' ');
299 if (!strncasecmp(method, "login", 5) ) {
300 if (strlen(argbuf) >= 7) {
301 smtp_get_user(&argbuf[6]);
304 CtdlEncodeBase64(buf, "Username:", 9);
305 cprintf("334 %s\r\n", buf);
306 SMTP->command_state = smtp_user;
311 if (!strncasecmp(method, "plain", 5) ) {
312 extract_token(encoded_authstring, argbuf, 1, ' ');
313 CtdlDecodeBase64(decoded_authstring,
315 strlen(encoded_authstring) );
316 strcpy(ident, decoded_authstring);
317 strcpy(user, &decoded_authstring[strlen(ident) + 1] );
318 strcpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2] );
320 if (CtdlLoginExistingUser(user) == login_ok) {
321 if (CtdlTryPassword(pass) == pass_ok) {
322 smtp_auth_greeting();
326 cprintf("504 5.7.4 Authentication failed.\r\n");
329 if (strncasecmp(method, "login", 5) ) {
330 cprintf("504 5.7.4 Unknown authentication method.\r\n");
338 * Back end for smtp_vrfy() command
340 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
342 if (!fuzzy_match(us, SMTP->vrfy_match)) {
344 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
350 * Implements the VRFY (verify user name) command.
351 * Performs fuzzy match on full user names.
353 void smtp_vrfy(char *argbuf) {
354 SMTP->vrfy_count = 0;
355 strcpy(SMTP->vrfy_match, argbuf);
356 ForEachUser(smtp_vrfy_backend, NULL);
358 if (SMTP->vrfy_count < 1) {
359 cprintf("550 5.1.1 String does not match anything.\r\n");
361 else if (SMTP->vrfy_count == 1) {
362 cprintf("250 %s <cit%ld@%s>\r\n",
363 SMTP->vrfy_buffer.fullname,
364 SMTP->vrfy_buffer.usernum,
367 else if (SMTP->vrfy_count > 1) {
368 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
377 * Back end for smtp_expn() command
379 void smtp_expn_backend(struct ctdluser *us, void *data) {
381 if (!fuzzy_match(us, SMTP->vrfy_match)) {
383 if (SMTP->vrfy_count >= 1) {
384 cprintf("250-%s <cit%ld@%s>\r\n",
385 SMTP->vrfy_buffer.fullname,
386 SMTP->vrfy_buffer.usernum,
391 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
397 * Implements the EXPN (expand user name) command.
398 * Performs fuzzy match on full user names.
400 void smtp_expn(char *argbuf) {
401 SMTP->vrfy_count = 0;
402 strcpy(SMTP->vrfy_match, argbuf);
403 ForEachUser(smtp_expn_backend, NULL);
405 if (SMTP->vrfy_count < 1) {
406 cprintf("550 5.1.1 String does not match anything.\r\n");
408 else if (SMTP->vrfy_count >= 1) {
409 cprintf("250 %s <cit%ld@%s>\r\n",
410 SMTP->vrfy_buffer.fullname,
411 SMTP->vrfy_buffer.usernum,
418 * Implements the RSET (reset state) command.
419 * Currently this just zeroes out the state buffer. If pointers to data
420 * allocated with malloc() are ever placed in the state buffer, we have to
421 * be sure to free() them first!
423 * Set do_response to nonzero to output the SMTP RSET response code.
425 void smtp_rset(int do_response) {
429 * Our entire SMTP state is discarded when a RSET command is issued,
430 * but we need to preserve this one little piece of information, so
431 * we save it for later.
433 is_lmtp = SMTP->is_lmtp;
435 memset(SMTP, 0, sizeof(struct citsmtp));
438 * It is somewhat ambiguous whether we want to log out when a RSET
439 * command is issued. Here's the code to do it. It is commented out
440 * because some clients (such as Pine) issue RSET commands before
441 * each message, but still expect to be logged in.
443 * if (CC->logged_in) {
449 * Reinstate this little piece of information we saved (see above).
451 SMTP->is_lmtp = is_lmtp;
454 cprintf("250 2.0.0 Zap!\r\n");
459 * Clear out the portions of the state buffer that need to be cleared out
460 * after the DATA command finishes.
462 void smtp_data_clear(void) {
463 strcpy(SMTP->from, "");
464 strcpy(SMTP->recipients, "");
465 SMTP->number_of_recipients = 0;
466 SMTP->delivery_mode = 0;
467 SMTP->message_originated_locally = 0;
473 * Implements the "MAIL From:" command
475 void smtp_mail(char *argbuf) {
480 if (strlen(SMTP->from) != 0) {
481 cprintf("503 5.1.0 Only one sender permitted\r\n");
485 if (strncasecmp(argbuf, "From:", 5)) {
486 cprintf("501 5.1.7 Syntax error\r\n");
490 strcpy(SMTP->from, &argbuf[5]);
492 stripallbut(SMTP->from, '<', '>');
494 /* We used to reject empty sender names, until it was brought to our
495 * attention that RFC1123 5.2.9 requires that this be allowed. So now
496 * we allow it, but replace the empty string with a fake
497 * address so we don't have to contend with the empty string causing
498 * other code to fail when it's expecting something there.
500 if (strlen(SMTP->from) == 0) {
501 strcpy(SMTP->from, "someone@somewhere.org");
504 /* If this SMTP connection is from a logged-in user, force the 'from'
505 * to be the user's Internet e-mail address as Citadel knows it.
508 strcpy(SMTP->from, CC->cs_inet_email);
509 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
510 SMTP->message_originated_locally = 1;
514 else if (SMTP->is_lmtp) {
515 /* Bypass forgery checking for LMTP */
518 /* Otherwise, make sure outsiders aren't trying to forge mail from
522 process_rfc822_addr(SMTP->from, user, node, name);
523 if (CtdlHostAlias(node) != hostalias_nomatch) {
525 "You must log in to send mail from %s\r\n",
527 strcpy(SMTP->from, "");
532 cprintf("250 2.0.0 Sender ok\r\n");
538 * Implements the "RCPT To:" command
540 void smtp_rcpt(char *argbuf) {
542 char message_to_spammer[SIZ];
543 struct recptypes *valid = NULL;
545 if (strlen(SMTP->from) == 0) {
546 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
550 if (strncasecmp(argbuf, "To:", 3)) {
551 cprintf("501 5.1.7 Syntax error\r\n");
555 strcpy(recp, &argbuf[3]);
557 stripallbut(recp, '<', '>');
559 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
560 cprintf("452 4.5.3 Too many recipients\r\n");
565 if ( (!CC->logged_in)
566 && (!SMTP->is_lmtp) ) {
567 if (rbl_check(message_to_spammer)) {
568 cprintf("550 %s\r\n", message_to_spammer);
569 /* no need to free(valid), it's not allocated yet */
574 valid = validate_recipients(recp);
575 if (valid->num_error > 0) {
576 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
581 if (valid->num_internet > 0) {
582 if ( (SMTP->message_originated_locally == 0)
583 && (SMTP->is_lmtp == 0) ) {
584 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
590 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
591 if (strlen(SMTP->recipients) > 0) {
592 strcat(SMTP->recipients, ",");
594 strcat(SMTP->recipients, recp);
595 SMTP->number_of_recipients += 1;
602 * Implements the DATA command
604 void smtp_data(void) {
606 struct CtdlMessage *msg;
609 struct recptypes *valid;
614 if (strlen(SMTP->from) == 0) {
615 cprintf("503 5.5.1 Need MAIL command first.\r\n");
619 if (SMTP->number_of_recipients < 1) {
620 cprintf("503 5.5.1 Need RCPT command first.\r\n");
624 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
626 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
629 if (body != NULL) snprintf(body, 4096,
630 "Received: from %s (%s [%s])\n"
638 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
641 "Unable to save message: internal error.\r\n");
645 lprintf(CTDL_DEBUG, "Converting message...\n");
646 msg = convert_internet_message(body);
648 /* If the user is locally authenticated, FORCE the From: header to
649 * show up as the real sender. Yes, this violates the RFC standard,
650 * but IT MAKES SENSE. If you prefer strict RFC adherence over
651 * common sense, you can disable this in the configuration.
653 * We also set the "message room name" ('O' field) to MAILROOM
654 * (which is Mail> on most systems) to prevent it from getting set
655 * to something ugly like "0000058008.Sent Items>" when the message
656 * is read with a Citadel client.
658 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
659 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
660 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
661 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
662 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
663 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
664 msg->cm_fields['A'] = strdup(CC->user.fullname);
665 msg->cm_fields['N'] = strdup(config.c_nodename);
666 msg->cm_fields['H'] = strdup(config.c_humannode);
667 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
668 msg->cm_fields['O'] = strdup(MAILROOM);
671 /* Submit the message into the Citadel system. */
672 valid = validate_recipients(SMTP->recipients);
674 /* If there are modules that want to scan this message before final
675 * submission (such as virus checkers or spam filters), call them now
676 * and give them an opportunity to reject the message.
678 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
680 if (scan_errors > 0) { /* We don't want this message! */
682 if (msg->cm_fields['0'] == NULL) {
683 msg->cm_fields['0'] = strdup(
684 "5.7.1 Message rejected by filter");
687 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
690 else { /* Ok, we'll accept this message. */
691 msgnum = CtdlSubmitMsg(msg, valid, "");
693 sprintf(result, "250 2.0.0 Message accepted.\r\n");
696 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
700 /* For SMTP and ESTMP, just print the result message. For LMTP, we
701 * have to print one result message for each recipient. Since there
702 * is nothing in Citadel which would cause different recipients to
703 * have different results, we can get away with just spitting out the
704 * same message once for each recipient.
707 for (i=0; i<SMTP->number_of_recipients; ++i) {
708 cprintf("%s", result);
712 cprintf("%s", result);
715 CtdlFreeMessage(msg);
717 smtp_data_clear(); /* clear out the buffers now */
722 * implements the STARTTLS command (Citadel API version)
725 void smtp_starttls(void)
727 char ok_response[SIZ];
728 char nosup_response[SIZ];
729 char error_response[SIZ];
732 "200 2.0.0 Begin TLS negotiation now\r\n");
733 sprintf(nosup_response,
734 "554 5.7.3 TLS not supported here\r\n");
735 sprintf(error_response,
736 "554 5.7.3 Internal error\r\n");
737 CtdlStartTLS(ok_response, nosup_response, error_response);
745 * Main command loop for SMTP sessions.
747 void smtp_command_loop(void) {
751 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
752 if (client_gets(cmdbuf) < 1) {
753 lprintf(CTDL_CRIT, "SMTP socket is broken. Ending session.\n");
757 lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
758 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
760 if (SMTP->command_state == smtp_user) {
761 smtp_get_user(cmdbuf);
764 else if (SMTP->command_state == smtp_password) {
765 smtp_get_pass(cmdbuf);
768 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
769 smtp_auth(&cmdbuf[5]);
772 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
776 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
777 smtp_expn(&cmdbuf[5]);
780 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
781 smtp_hello(&cmdbuf[5], 0);
784 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
785 smtp_hello(&cmdbuf[5], 1);
788 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
789 smtp_hello(&cmdbuf[5], 2);
792 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
796 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
797 smtp_mail(&cmdbuf[5]);
800 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
801 cprintf("250 NOOP\r\n");
804 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
805 cprintf("221 Goodbye...\r\n");
810 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
811 smtp_rcpt(&cmdbuf[5]);
814 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
818 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
822 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
823 smtp_vrfy(&cmdbuf[5]);
827 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
836 /*****************************************************************************/
837 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
838 /*****************************************************************************/
845 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
848 void smtp_try(const char *key, const char *addr, int *status,
849 char *dsn, size_t n, long msgnum)
856 char user[SIZ], node[SIZ], name[SIZ];
862 size_t blocksize = 0;
865 /* Parse out the host portion of the recipient address */
866 process_rfc822_addr(addr, user, node, name);
868 lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
871 /* Load the message out of the database into a temp file */
873 if (msg_fp == NULL) {
875 snprintf(dsn, n, "Error creating temporary file");
879 CtdlRedirectOutput(msg_fp, -1);
880 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
881 CtdlRedirectOutput(NULL, -1);
882 fseek(msg_fp, 0L, SEEK_END);
883 msg_size = ftell(msg_fp);
887 /* Extract something to send later in the 'MAIL From:' command */
888 strcpy(mailfrom, "");
892 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
893 if (!strncasecmp(buf, "From:", 5)) {
894 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
896 for (i=0; i<strlen(mailfrom); ++i) {
897 if (!isprint(mailfrom[i])) {
898 strcpy(&mailfrom[i], &mailfrom[i+1]);
903 /* Strip out parenthesized names */
906 for (i=0; i<strlen(mailfrom); ++i) {
907 if (mailfrom[i] == '(') lp = i;
908 if (mailfrom[i] == ')') rp = i;
910 if ((lp>0)&&(rp>lp)) {
911 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
914 /* Prefer brokketized names */
917 for (i=0; i<strlen(mailfrom); ++i) {
918 if (mailfrom[i] == '<') lp = i;
919 if (mailfrom[i] == '>') rp = i;
921 if ( (lp>=0) && (rp>lp) ) {
923 strcpy(mailfrom, &mailfrom[lp]);
928 } while (scan_done == 0);
929 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
931 /* Figure out what mail exchanger host we have to connect to */
932 num_mxhosts = getmx(mxhosts, node);
933 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
934 if (num_mxhosts < 1) {
936 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
941 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
942 extract(buf, mxhosts, mx);
943 lprintf(CTDL_DEBUG, "Trying <%s>\n", buf);
944 sock = sock_connect(buf, "25", "tcp");
945 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
946 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
947 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
951 *status = 4; /* dsn is already filled in */
955 /* Process the SMTP greeting from the server */
956 if (ml_sock_gets(sock, buf) < 0) {
958 strcpy(dsn, "Connection broken during SMTP conversation");
961 lprintf(CTDL_DEBUG, "<%s\n", buf);
965 safestrncpy(dsn, &buf[4], 1023);
970 safestrncpy(dsn, &buf[4], 1023);
975 /* At this point we know we are talking to a real SMTP server */
977 /* Do a HELO command */
978 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
979 lprintf(CTDL_DEBUG, ">%s", buf);
980 sock_write(sock, buf, strlen(buf));
981 if (ml_sock_gets(sock, buf) < 0) {
983 strcpy(dsn, "Connection broken during SMTP HELO");
986 lprintf(CTDL_DEBUG, "<%s\n", buf);
990 safestrncpy(dsn, &buf[4], 1023);
995 safestrncpy(dsn, &buf[4], 1023);
1001 /* HELO succeeded, now try the MAIL From: command */
1002 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1003 lprintf(CTDL_DEBUG, ">%s", buf);
1004 sock_write(sock, buf, strlen(buf));
1005 if (ml_sock_gets(sock, buf) < 0) {
1007 strcpy(dsn, "Connection broken during SMTP MAIL");
1010 lprintf(CTDL_DEBUG, "<%s\n", buf);
1011 if (buf[0] != '2') {
1012 if (buf[0] == '4') {
1014 safestrncpy(dsn, &buf[4], 1023);
1019 safestrncpy(dsn, &buf[4], 1023);
1025 /* MAIL succeeded, now try the RCPT To: command */
1026 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
1027 lprintf(CTDL_DEBUG, ">%s", buf);
1028 sock_write(sock, buf, strlen(buf));
1029 if (ml_sock_gets(sock, buf) < 0) {
1031 strcpy(dsn, "Connection broken during SMTP RCPT");
1034 lprintf(CTDL_DEBUG, "<%s\n", buf);
1035 if (buf[0] != '2') {
1036 if (buf[0] == '4') {
1038 safestrncpy(dsn, &buf[4], 1023);
1043 safestrncpy(dsn, &buf[4], 1023);
1049 /* RCPT succeeded, now try the DATA command */
1050 lprintf(CTDL_DEBUG, ">DATA\n");
1051 sock_write(sock, "DATA\r\n", 6);
1052 if (ml_sock_gets(sock, buf) < 0) {
1054 strcpy(dsn, "Connection broken during SMTP DATA");
1057 lprintf(CTDL_DEBUG, "<%s\n", buf);
1058 if (buf[0] != '3') {
1059 if (buf[0] == '4') {
1061 safestrncpy(dsn, &buf[4], 1023);
1066 safestrncpy(dsn, &buf[4], 1023);
1071 /* If we reach this point, the server is expecting data */
1073 while (msg_size > 0) {
1074 blocksize = sizeof(buf);
1075 if (blocksize > msg_size) blocksize = msg_size;
1076 fread(buf, blocksize, 1, msg_fp);
1077 sock_write(sock, buf, blocksize);
1078 msg_size -= blocksize;
1080 if (buf[blocksize-1] != 10) {
1081 lprintf(CTDL_WARNING, "Possible problem: message did not "
1082 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1086 sock_write(sock, ".\r\n", 3);
1087 if (ml_sock_gets(sock, buf) < 0) {
1089 strcpy(dsn, "Connection broken during SMTP message transmit");
1092 lprintf(CTDL_DEBUG, "%s\n", buf);
1093 if (buf[0] != '2') {
1094 if (buf[0] == '4') {
1096 safestrncpy(dsn, &buf[4], 1023);
1101 safestrncpy(dsn, &buf[4], 1023);
1107 safestrncpy(dsn, &buf[4], 1023);
1110 lprintf(CTDL_DEBUG, ">QUIT\n");
1111 sock_write(sock, "QUIT\r\n", 6);
1112 ml_sock_gets(sock, buf);
1113 lprintf(CTDL_DEBUG, "<%s\n", buf);
1114 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1117 bail: if (msg_fp != NULL) fclose(msg_fp);
1125 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1126 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1127 * a "bounce" message (delivery status notification).
1129 void smtp_do_bounce(char *instr) {
1137 char bounceto[1024];
1138 int num_bounces = 0;
1139 int bounce_this = 0;
1140 long bounce_msgid = (-1);
1141 time_t submitted = 0L;
1142 struct CtdlMessage *bmsg = NULL;
1144 struct recptypes *valid;
1145 int successful_bounce = 0;
1147 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1148 strcpy(bounceto, "");
1150 lines = num_tokens(instr, '\n');
1153 /* See if it's time to give up on delivery of this message */
1154 for (i=0; i<lines; ++i) {
1155 extract_token(buf, instr, i, '\n');
1156 extract(key, buf, 0);
1157 extract(addr, buf, 1);
1158 if (!strcasecmp(key, "submitted")) {
1159 submitted = atol(addr);
1163 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1169 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1170 if (bmsg == NULL) return;
1171 memset(bmsg, 0, sizeof(struct CtdlMessage));
1173 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1174 bmsg->cm_anon_type = MES_NORMAL;
1175 bmsg->cm_format_type = 1;
1176 bmsg->cm_fields['A'] = strdup("Citadel");
1177 bmsg->cm_fields['O'] = strdup(MAILROOM);
1178 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1180 if (give_up) bmsg->cm_fields['M'] = strdup(
1181 "A message you sent could not be delivered to some or all of its recipients\n"
1182 "due to prolonged unavailability of its destination(s).\n"
1183 "Giving up on the following addresses:\n\n"
1186 else bmsg->cm_fields['M'] = strdup(
1187 "A message you sent could not be delivered to some or all of its recipients.\n"
1188 "The following addresses were undeliverable:\n\n"
1192 * Now go through the instructions checking for stuff.
1194 for (i=0; i<lines; ++i) {
1195 extract_token(buf, instr, i, '\n');
1196 extract(key, buf, 0);
1197 extract(addr, buf, 1);
1198 status = extract_int(buf, 2);
1199 extract(dsn, buf, 3);
1202 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1203 key, addr, status, dsn);
1205 if (!strcasecmp(key, "bounceto")) {
1206 strcpy(bounceto, addr);
1210 (!strcasecmp(key, "local"))
1211 || (!strcasecmp(key, "remote"))
1212 || (!strcasecmp(key, "ignet"))
1213 || (!strcasecmp(key, "room"))
1215 if (status == 5) bounce_this = 1;
1216 if (give_up) bounce_this = 1;
1222 if (bmsg->cm_fields['M'] == NULL) {
1223 lprintf(CTDL_ERR, "ERROR ... M field is null "
1224 "(%s:%d)\n", __FILE__, __LINE__);
1227 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1228 strlen(bmsg->cm_fields['M']) + 1024 );
1229 strcat(bmsg->cm_fields['M'], addr);
1230 strcat(bmsg->cm_fields['M'], ": ");
1231 strcat(bmsg->cm_fields['M'], dsn);
1232 strcat(bmsg->cm_fields['M'], "\n");
1234 remove_token(instr, i, '\n');
1240 /* Deliver the bounce if there's anything worth mentioning */
1241 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1242 if (num_bounces > 0) {
1244 /* First try the user who sent the message */
1245 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1246 if (strlen(bounceto) == 0) {
1247 lprintf(CTDL_ERR, "No bounce address specified\n");
1248 bounce_msgid = (-1L);
1251 /* Can we deliver the bounce to the original sender? */
1252 valid = validate_recipients(bounceto);
1253 if (valid != NULL) {
1254 if (valid->num_error == 0) {
1255 CtdlSubmitMsg(bmsg, valid, "");
1256 successful_bounce = 1;
1260 /* If not, post it in the Aide> room */
1261 if (successful_bounce == 0) {
1262 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1265 /* Free up the memory we used */
1266 if (valid != NULL) {
1271 CtdlFreeMessage(bmsg);
1272 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1277 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1278 * set of delivery instructions for completed deliveries and remove them.
1280 * It returns the number of incomplete deliveries remaining.
1282 int smtp_purge_completed_deliveries(char *instr) {
1293 lines = num_tokens(instr, '\n');
1294 for (i=0; i<lines; ++i) {
1295 extract_token(buf, instr, i, '\n');
1296 extract(key, buf, 0);
1297 extract(addr, buf, 1);
1298 status = extract_int(buf, 2);
1299 extract(dsn, buf, 3);
1304 (!strcasecmp(key, "local"))
1305 || (!strcasecmp(key, "remote"))
1306 || (!strcasecmp(key, "ignet"))
1307 || (!strcasecmp(key, "room"))
1309 if (status == 2) completed = 1;
1314 remove_token(instr, i, '\n');
1327 * Called by smtp_do_queue() to handle an individual message.
1329 void smtp_do_procmsg(long msgnum, void *userdata) {
1330 struct CtdlMessage *msg;
1332 char *results = NULL;
1340 long text_msgid = (-1);
1341 int incomplete_deliveries_remaining;
1342 time_t attempted = 0L;
1343 time_t last_attempted = 0L;
1344 time_t retry = SMTP_RETRY_INTERVAL;
1346 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1348 msg = CtdlFetchMessage(msgnum);
1350 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1354 instr = strdup(msg->cm_fields['M']);
1355 CtdlFreeMessage(msg);
1357 /* Strip out the headers amd any other non-instruction line */
1358 lines = num_tokens(instr, '\n');
1359 for (i=0; i<lines; ++i) {
1360 extract_token(buf, instr, i, '\n');
1361 if (num_tokens(buf, '|') < 2) {
1362 remove_token(instr, i, '\n');
1368 /* Learn the message ID and find out about recent delivery attempts */
1369 lines = num_tokens(instr, '\n');
1370 for (i=0; i<lines; ++i) {
1371 extract_token(buf, instr, i, '\n');
1372 extract(key, buf, 0);
1373 if (!strcasecmp(key, "msgid")) {
1374 text_msgid = extract_long(buf, 1);
1376 if (!strcasecmp(key, "retry")) {
1377 /* double the retry interval after each attempt */
1378 retry = extract_long(buf, 1) * 2L;
1379 if (retry > SMTP_RETRY_MAX) {
1380 retry = SMTP_RETRY_MAX;
1382 remove_token(instr, i, '\n');
1384 if (!strcasecmp(key, "attempted")) {
1385 attempted = extract_long(buf, 1);
1386 if (attempted > last_attempted)
1387 last_attempted = attempted;
1392 * Postpone delivery if we've already tried recently.
1394 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1395 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1402 * Bail out if there's no actual message associated with this
1404 if (text_msgid < 0L) {
1405 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1410 /* Plow through the instructions looking for 'remote' directives and
1411 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1412 * were experienced and it's time to try again)
1414 lines = num_tokens(instr, '\n');
1415 for (i=0; i<lines; ++i) {
1416 extract_token(buf, instr, i, '\n');
1417 extract(key, buf, 0);
1418 extract(addr, buf, 1);
1419 status = extract_int(buf, 2);
1420 extract(dsn, buf, 3);
1421 if ( (!strcasecmp(key, "remote"))
1422 && ((status==0)||(status==3)||(status==4)) ) {
1424 /* Remove this "remote" instruction from the set,
1425 * but replace the set's final newline if
1426 * remove_token() stripped it. It has to be there.
1428 remove_token(instr, i, '\n');
1429 if (instr[strlen(instr)-1] != '\n') {
1430 strcat(instr, "\n");
1435 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1436 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1438 if (results == NULL) {
1439 results = malloc(1024);
1440 memset(results, 0, 1024);
1443 results = realloc(results,
1444 strlen(results) + 1024);
1446 snprintf(&results[strlen(results)], 1024,
1448 key, addr, status, dsn);
1453 if (results != NULL) {
1454 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1455 strcat(instr, results);
1460 /* Generate 'bounce' messages */
1461 smtp_do_bounce(instr);
1463 /* Go through the delivery list, deleting completed deliveries */
1464 incomplete_deliveries_remaining =
1465 smtp_purge_completed_deliveries(instr);
1469 * No delivery instructions remain, so delete both the instructions
1470 * message and the message message.
1472 if (incomplete_deliveries_remaining <= 0) {
1473 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1474 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1479 * Uncompleted delivery instructions remain, so delete the old
1480 * instructions and replace with the updated ones.
1482 if (incomplete_deliveries_remaining > 0) {
1483 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1484 msg = malloc(sizeof(struct CtdlMessage));
1485 memset(msg, 0, sizeof(struct CtdlMessage));
1486 msg->cm_magic = CTDLMESSAGE_MAGIC;
1487 msg->cm_anon_type = MES_NORMAL;
1488 msg->cm_format_type = FMT_RFC822;
1489 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1490 snprintf(msg->cm_fields['M'],
1492 "Content-type: %s\n\n%s\n"
1495 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1497 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1498 CtdlFreeMessage(msg);
1508 * Run through the queue sending out messages.
1510 void smtp_do_queue(void) {
1511 static int doing_queue = 0;
1514 * This is a simple concurrency check to make sure only one queue run
1515 * is done at a time. We could do this with a mutex, but since we
1516 * don't really require extremely fine granularity here, we'll do it
1517 * with a static variable instead.
1519 if (doing_queue) return;
1523 * Go ahead and run the queue
1525 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1527 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1528 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1531 CtdlForEachMessage(MSGS_ALL, 0L,
1532 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1534 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1541 /*****************************************************************************/
1542 /* SMTP UTILITY COMMANDS */
1543 /*****************************************************************************/
1545 void cmd_smtp(char *argbuf) {
1552 if (CtdlAccessCheck(ac_aide)) return;
1554 extract(cmd, argbuf, 0);
1556 if (!strcasecmp(cmd, "mx")) {
1557 extract(node, argbuf, 1);
1558 num_mxhosts = getmx(buf, node);
1559 cprintf("%d %d MX hosts listed for %s\n",
1560 LISTING_FOLLOWS, num_mxhosts, node);
1561 for (i=0; i<num_mxhosts; ++i) {
1562 extract(node, buf, i);
1563 cprintf("%s\n", node);
1569 else if (!strcasecmp(cmd, "runqueue")) {
1571 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1576 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1583 * Initialize the SMTP outbound queue
1585 void smtp_init_spoolout(void) {
1586 struct ctdlroom qrbuf;
1589 * Create the room. This will silently fail if the room already
1590 * exists, and that's perfectly ok, because we want it to exist.
1592 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0);
1595 * Make sure it's set to be a "system room" so it doesn't show up
1596 * in the <K>nown rooms list for Aides.
1598 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1599 qrbuf.QRflags2 |= QR2_SYSTEM;
1607 /*****************************************************************************/
1608 /* MODULE INITIALIZATION STUFF */
1609 /*****************************************************************************/
1612 char *serv_smtp_init(void)
1614 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1619 CtdlRegisterServiceHook(0, /* ...and locally */
1624 smtp_init_spoolout();
1625 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1626 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");