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);
200 cprintf("250-PIPELINING\r\n");
201 cprintf("250-AUTH LOGIN PLAIN\r\n");
202 cprintf("250-AUTH=LOGIN PLAIN\r\n");
204 cprintf("250-STARTTLS\r\n");
206 cprintf("250 ENHANCEDSTATUSCODES\r\n");
213 * Implement HELP command.
215 void smtp_help(void) {
216 cprintf("214-Commands accepted:\r\n");
217 cprintf("214- DATA\r\n");
218 cprintf("214- EHLO\r\n");
219 cprintf("214- EXPN\r\n");
220 cprintf("214- HELO\r\n");
221 cprintf("214- HELP\r\n");
222 cprintf("214- MAIL\r\n");
223 cprintf("214- NOOP\r\n");
224 cprintf("214- QUIT\r\n");
225 cprintf("214- RCPT\r\n");
226 cprintf("214- RSET\r\n");
227 cprintf("214- VRFY\r\n");
235 void smtp_get_user(char *argbuf) {
239 CtdlDecodeBase64(username, argbuf, SIZ);
240 lprintf(CTDL_DEBUG, "Trying <%s>\n", username);
241 if (CtdlLoginExistingUser(username) == login_ok) {
242 CtdlEncodeBase64(buf, "Password:", 9);
243 cprintf("334 %s\r\n", buf);
244 SMTP->command_state = smtp_password;
247 cprintf("500 5.7.0 No such user.\r\n");
248 SMTP->command_state = smtp_command;
256 void smtp_get_pass(char *argbuf) {
259 CtdlDecodeBase64(password, argbuf, SIZ);
260 lprintf(CTDL_DEBUG, "Trying <%s>\n", password);
261 if (CtdlTryPassword(password) == pass_ok) {
262 smtp_auth_greeting();
265 cprintf("535 5.7.0 Authentication failed.\r\n");
267 SMTP->command_state = smtp_command;
274 void smtp_auth(char *argbuf) {
277 char encoded_authstring[SIZ];
278 char decoded_authstring[SIZ];
284 cprintf("504 5.7.4 Already logged in.\r\n");
288 extract_token(method, argbuf, 0, ' ');
290 if (!strncasecmp(method, "login", 5) ) {
291 if (strlen(argbuf) >= 7) {
292 smtp_get_user(&argbuf[6]);
295 CtdlEncodeBase64(buf, "Username:", 9);
296 cprintf("334 %s\r\n", buf);
297 SMTP->command_state = smtp_user;
302 if (!strncasecmp(method, "plain", 5) ) {
303 extract_token(encoded_authstring, argbuf, 1, ' ');
304 CtdlDecodeBase64(decoded_authstring,
306 strlen(encoded_authstring) );
307 strcpy(ident, decoded_authstring);
308 strcpy(user, &decoded_authstring[strlen(ident) + 1] );
309 strcpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2] );
311 if (CtdlLoginExistingUser(user) == login_ok) {
312 if (CtdlTryPassword(pass) == pass_ok) {
313 smtp_auth_greeting();
317 cprintf("504 5.7.4 Authentication failed.\r\n");
320 if (strncasecmp(method, "login", 5) ) {
321 cprintf("504 5.7.4 Unknown authentication method.\r\n");
329 * Back end for smtp_vrfy() command
331 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
333 if (!fuzzy_match(us, SMTP->vrfy_match)) {
335 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
341 * Implements the VRFY (verify user name) command.
342 * Performs fuzzy match on full user names.
344 void smtp_vrfy(char *argbuf) {
345 SMTP->vrfy_count = 0;
346 strcpy(SMTP->vrfy_match, argbuf);
347 ForEachUser(smtp_vrfy_backend, NULL);
349 if (SMTP->vrfy_count < 1) {
350 cprintf("550 5.1.1 String does not match anything.\r\n");
352 else if (SMTP->vrfy_count == 1) {
353 cprintf("250 %s <cit%ld@%s>\r\n",
354 SMTP->vrfy_buffer.fullname,
355 SMTP->vrfy_buffer.usernum,
358 else if (SMTP->vrfy_count > 1) {
359 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
368 * Back end for smtp_expn() command
370 void smtp_expn_backend(struct ctdluser *us, void *data) {
372 if (!fuzzy_match(us, SMTP->vrfy_match)) {
374 if (SMTP->vrfy_count >= 1) {
375 cprintf("250-%s <cit%ld@%s>\r\n",
376 SMTP->vrfy_buffer.fullname,
377 SMTP->vrfy_buffer.usernum,
382 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
388 * Implements the EXPN (expand user name) command.
389 * Performs fuzzy match on full user names.
391 void smtp_expn(char *argbuf) {
392 SMTP->vrfy_count = 0;
393 strcpy(SMTP->vrfy_match, argbuf);
394 ForEachUser(smtp_expn_backend, NULL);
396 if (SMTP->vrfy_count < 1) {
397 cprintf("550 5.1.1 String does not match anything.\r\n");
399 else if (SMTP->vrfy_count >= 1) {
400 cprintf("250 %s <cit%ld@%s>\r\n",
401 SMTP->vrfy_buffer.fullname,
402 SMTP->vrfy_buffer.usernum,
409 * Implements the RSET (reset state) command.
410 * Currently this just zeroes out the state buffer. If pointers to data
411 * allocated with mallok() are ever placed in the state buffer, we have to
412 * be sure to phree() them first!
414 * Set do_response to nonzero to output the SMTP RSET response code.
416 void smtp_rset(int do_response) {
420 * Our entire SMTP state is discarded when a RSET command is issued,
421 * but we need to preserve this one little piece of information, so
422 * we save it for later.
424 is_lmtp = SMTP->is_lmtp;
426 memset(SMTP, 0, sizeof(struct citsmtp));
429 * It is somewhat ambiguous whether we want to log out when a RSET
430 * command is issued. Here's the code to do it. It is commented out
431 * because some clients (such as Pine) issue RSET commands before
432 * each message, but still expect to be logged in.
434 * if (CC->logged_in) {
440 * Reinstate this little piece of information we saved (see above).
442 SMTP->is_lmtp = is_lmtp;
445 cprintf("250 2.0.0 Zap!\r\n");
450 * Clear out the portions of the state buffer that need to be cleared out
451 * after the DATA command finishes.
453 void smtp_data_clear(void) {
454 strcpy(SMTP->from, "");
455 strcpy(SMTP->recipients, "");
456 SMTP->number_of_recipients = 0;
457 SMTP->delivery_mode = 0;
458 SMTP->message_originated_locally = 0;
464 * Implements the "MAIL From:" command
466 void smtp_mail(char *argbuf) {
471 if (strlen(SMTP->from) != 0) {
472 cprintf("503 5.1.0 Only one sender permitted\r\n");
476 if (strncasecmp(argbuf, "From:", 5)) {
477 cprintf("501 5.1.7 Syntax error\r\n");
481 strcpy(SMTP->from, &argbuf[5]);
483 stripallbut(SMTP->from, '<', '>');
485 /* We used to reject empty sender names, until it was brought to our
486 * attention that RFC1123 5.2.9 requires that this be allowed. So now
487 * we allow it, but replace the empty string with a fake
488 * address so we don't have to contend with the empty string causing
489 * other code to fail when it's expecting something there.
491 if (strlen(SMTP->from) == 0) {
492 strcpy(SMTP->from, "someone@somewhere.org");
495 /* If this SMTP connection is from a logged-in user, force the 'from'
496 * to be the user's Internet e-mail address as Citadel knows it.
499 strcpy(SMTP->from, CC->cs_inet_email);
500 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
501 SMTP->message_originated_locally = 1;
505 else if (SMTP->is_lmtp) {
506 /* Bypass forgery checking for LMTP */
509 /* Otherwise, make sure outsiders aren't trying to forge mail from
513 process_rfc822_addr(SMTP->from, user, node, name);
514 if (CtdlHostAlias(node) != hostalias_nomatch) {
516 "You must log in to send mail from %s\r\n",
518 strcpy(SMTP->from, "");
523 cprintf("250 2.0.0 Sender ok\r\n");
529 * Implements the "RCPT To:" command
531 void smtp_rcpt(char *argbuf) {
533 char message_to_spammer[SIZ];
534 struct recptypes *valid = NULL;
536 if (strlen(SMTP->from) == 0) {
537 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
541 if (strncasecmp(argbuf, "To:", 3)) {
542 cprintf("501 5.1.7 Syntax error\r\n");
546 strcpy(recp, &argbuf[3]);
548 stripallbut(recp, '<', '>');
550 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
551 cprintf("452 4.5.3 Too many recipients\r\n");
556 if ( (!CC->logged_in)
557 && (!SMTP->is_lmtp) ) {
558 if (rbl_check(message_to_spammer)) {
559 cprintf("550 %s\r\n", message_to_spammer);
560 /* no need to phree(valid), it's not allocated yet */
565 valid = validate_recipients(recp);
566 if (valid->num_error > 0) {
567 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
572 if (valid->num_internet > 0) {
573 if ( (SMTP->message_originated_locally == 0)
574 && (SMTP->is_lmtp == 0) ) {
575 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
581 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
582 if (strlen(SMTP->recipients) > 0) {
583 strcat(SMTP->recipients, ",");
585 strcat(SMTP->recipients, recp);
586 SMTP->number_of_recipients += 1;
593 * Implements the DATA command
595 void smtp_data(void) {
597 struct CtdlMessage *msg;
600 struct recptypes *valid;
605 if (strlen(SMTP->from) == 0) {
606 cprintf("503 5.5.1 Need MAIL command first.\r\n");
610 if (SMTP->number_of_recipients < 1) {
611 cprintf("503 5.5.1 Need RCPT command first.\r\n");
615 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
617 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
620 if (body != NULL) snprintf(body, 4096,
621 "Received: from %s (%s [%s])\n"
629 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
632 "Unable to save message: internal error.\r\n");
636 lprintf(CTDL_DEBUG, "Converting message...\n");
637 msg = convert_internet_message(body);
639 /* If the user is locally authenticated, FORCE the From: header to
640 * show up as the real sender. Yes, this violates the RFC standard,
641 * but IT MAKES SENSE. If you prefer strict RFC adherence over
642 * common sense, you can disable this in the configuration.
644 * We also set the "message room name" ('O' field) to MAILROOM
645 * (which is Mail> on most systems) to prevent it from getting set
646 * to something ugly like "0000058008.Sent Items>" when the message
647 * is read with a Citadel client.
649 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
650 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
651 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
652 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
653 if (msg->cm_fields['F'] != NULL) phree(msg->cm_fields['F']);
654 if (msg->cm_fields['O'] != NULL) phree(msg->cm_fields['O']);
655 msg->cm_fields['A'] = strdoop(CC->user.fullname);
656 msg->cm_fields['N'] = strdoop(config.c_nodename);
657 msg->cm_fields['H'] = strdoop(config.c_humannode);
658 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
659 msg->cm_fields['O'] = strdoop(MAILROOM);
662 /* Submit the message into the Citadel system. */
663 valid = validate_recipients(SMTP->recipients);
665 /* If there are modules that want to scan this message before final
666 * submission (such as virus checkers or spam filters), call them now
667 * and give them an opportunity to reject the message.
669 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
671 if (scan_errors > 0) { /* We don't want this message! */
673 if (msg->cm_fields['0'] == NULL) {
674 msg->cm_fields['0'] = strdoop(
675 "5.7.1 Message rejected by filter");
678 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
681 else { /* Ok, we'll accept this message. */
682 msgnum = CtdlSubmitMsg(msg, valid, "");
684 sprintf(result, "250 2.0.0 Message accepted.\r\n");
687 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
691 /* For SMTP and ESTMP, just print the result message. For LMTP, we
692 * have to print one result message for each recipient. Since there
693 * is nothing in Citadel which would cause different recipients to
694 * have different results, we can get away with just spitting out the
695 * same message once for each recipient.
698 for (i=0; i<SMTP->number_of_recipients; ++i) {
699 cprintf("%s", result);
703 cprintf("%s", result);
706 CtdlFreeMessage(msg);
708 smtp_data_clear(); /* clear out the buffers now */
713 * implements the STARTTLS command (Citadel API version)
716 void smtp_starttls(void)
718 char ok_response[SIZ];
719 char nosup_response[SIZ];
720 char error_response[SIZ];
723 "200 2.0.0 Begin TLS negotiation now\r\n");
724 sprintf(nosup_response,
725 "554 5.7.3 TLS not supported here\r\n");
726 sprintf(error_response,
727 "554 5.7.3 Internal error\r\n");
728 CtdlStartTLS(ok_response, nosup_response, error_response);
736 * Main command loop for SMTP sessions.
738 void smtp_command_loop(void) {
742 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
743 if (client_gets(cmdbuf) < 1) {
744 lprintf(CTDL_CRIT, "SMTP socket is broken. Ending session.\n");
748 lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
749 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
751 if (SMTP->command_state == smtp_user) {
752 smtp_get_user(cmdbuf);
755 else if (SMTP->command_state == smtp_password) {
756 smtp_get_pass(cmdbuf);
759 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
760 smtp_auth(&cmdbuf[5]);
763 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
767 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
768 smtp_expn(&cmdbuf[5]);
771 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
772 smtp_hello(&cmdbuf[5], 0);
775 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
776 smtp_hello(&cmdbuf[5], 1);
779 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
780 smtp_hello(&cmdbuf[5], 2);
783 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
787 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
788 smtp_mail(&cmdbuf[5]);
791 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
792 cprintf("250 NOOP\r\n");
795 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
796 cprintf("221 Goodbye...\r\n");
801 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
802 smtp_rcpt(&cmdbuf[5]);
805 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
809 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
813 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
814 smtp_vrfy(&cmdbuf[5]);
818 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
826 /*****************************************************************************/
827 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
828 /*****************************************************************************/
835 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
838 void smtp_try(const char *key, const char *addr, int *status,
839 char *dsn, size_t n, long msgnum)
846 char user[SIZ], node[SIZ], name[SIZ];
852 size_t blocksize = 0;
855 /* Parse out the host portion of the recipient address */
856 process_rfc822_addr(addr, user, node, name);
858 lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
861 /* Load the message out of the database into a temp file */
863 if (msg_fp == NULL) {
865 snprintf(dsn, n, "Error creating temporary file");
869 CtdlRedirectOutput(msg_fp, -1);
870 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
871 CtdlRedirectOutput(NULL, -1);
872 fseek(msg_fp, 0L, SEEK_END);
873 msg_size = ftell(msg_fp);
877 /* Extract something to send later in the 'MAIL From:' command */
878 strcpy(mailfrom, "");
882 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
883 if (!strncasecmp(buf, "From:", 5)) {
884 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
886 for (i=0; i<strlen(mailfrom); ++i) {
887 if (!isprint(mailfrom[i])) {
888 strcpy(&mailfrom[i], &mailfrom[i+1]);
893 /* Strip out parenthesized names */
896 for (i=0; i<strlen(mailfrom); ++i) {
897 if (mailfrom[i] == '(') lp = i;
898 if (mailfrom[i] == ')') rp = i;
900 if ((lp>0)&&(rp>lp)) {
901 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
904 /* Prefer brokketized names */
907 for (i=0; i<strlen(mailfrom); ++i) {
908 if (mailfrom[i] == '<') lp = i;
909 if (mailfrom[i] == '>') rp = i;
911 if ( (lp>=0) && (rp>lp) ) {
913 strcpy(mailfrom, &mailfrom[lp]);
918 } while (scan_done == 0);
919 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
921 /* Figure out what mail exchanger host we have to connect to */
922 num_mxhosts = getmx(mxhosts, node);
923 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
924 if (num_mxhosts < 1) {
926 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
931 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
932 extract(buf, mxhosts, mx);
933 lprintf(CTDL_DEBUG, "Trying <%s>\n", buf);
934 sock = sock_connect(buf, "25", "tcp");
935 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
936 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
937 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
941 *status = 4; /* dsn is already filled in */
945 /* Process the SMTP greeting from the server */
946 if (ml_sock_gets(sock, buf) < 0) {
948 strcpy(dsn, "Connection broken during SMTP conversation");
951 lprintf(CTDL_DEBUG, "<%s\n", buf);
955 safestrncpy(dsn, &buf[4], 1023);
960 safestrncpy(dsn, &buf[4], 1023);
965 /* At this point we know we are talking to a real SMTP server */
967 /* Do a HELO command */
968 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
969 lprintf(CTDL_DEBUG, ">%s", buf);
970 sock_write(sock, buf, strlen(buf));
971 if (ml_sock_gets(sock, buf) < 0) {
973 strcpy(dsn, "Connection broken during SMTP HELO");
976 lprintf(CTDL_DEBUG, "<%s\n", buf);
980 safestrncpy(dsn, &buf[4], 1023);
985 safestrncpy(dsn, &buf[4], 1023);
991 /* HELO succeeded, now try the MAIL From: command */
992 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
993 lprintf(CTDL_DEBUG, ">%s", buf);
994 sock_write(sock, buf, strlen(buf));
995 if (ml_sock_gets(sock, buf) < 0) {
997 strcpy(dsn, "Connection broken during SMTP MAIL");
1000 lprintf(CTDL_DEBUG, "<%s\n", buf);
1001 if (buf[0] != '2') {
1002 if (buf[0] == '4') {
1004 safestrncpy(dsn, &buf[4], 1023);
1009 safestrncpy(dsn, &buf[4], 1023);
1015 /* MAIL succeeded, now try the RCPT To: command */
1016 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
1017 lprintf(CTDL_DEBUG, ">%s", buf);
1018 sock_write(sock, buf, strlen(buf));
1019 if (ml_sock_gets(sock, buf) < 0) {
1021 strcpy(dsn, "Connection broken during SMTP RCPT");
1024 lprintf(CTDL_DEBUG, "<%s\n", buf);
1025 if (buf[0] != '2') {
1026 if (buf[0] == '4') {
1028 safestrncpy(dsn, &buf[4], 1023);
1033 safestrncpy(dsn, &buf[4], 1023);
1039 /* RCPT succeeded, now try the DATA command */
1040 lprintf(CTDL_DEBUG, ">DATA\n");
1041 sock_write(sock, "DATA\r\n", 6);
1042 if (ml_sock_gets(sock, buf) < 0) {
1044 strcpy(dsn, "Connection broken during SMTP DATA");
1047 lprintf(CTDL_DEBUG, "<%s\n", buf);
1048 if (buf[0] != '3') {
1049 if (buf[0] == '4') {
1051 safestrncpy(dsn, &buf[4], 1023);
1056 safestrncpy(dsn, &buf[4], 1023);
1061 /* If we reach this point, the server is expecting data */
1063 while (msg_size > 0) {
1064 blocksize = sizeof(buf);
1065 if (blocksize > msg_size) blocksize = msg_size;
1066 fread(buf, blocksize, 1, msg_fp);
1067 sock_write(sock, buf, blocksize);
1068 msg_size -= blocksize;
1070 if (buf[blocksize-1] != 10) {
1071 lprintf(CTDL_WARNING, "Possible problem: message did not "
1072 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1076 sock_write(sock, ".\r\n", 3);
1077 if (ml_sock_gets(sock, buf) < 0) {
1079 strcpy(dsn, "Connection broken during SMTP message transmit");
1082 lprintf(CTDL_DEBUG, "%s\n", buf);
1083 if (buf[0] != '2') {
1084 if (buf[0] == '4') {
1086 safestrncpy(dsn, &buf[4], 1023);
1091 safestrncpy(dsn, &buf[4], 1023);
1097 safestrncpy(dsn, &buf[4], 1023);
1100 lprintf(CTDL_DEBUG, ">QUIT\n");
1101 sock_write(sock, "QUIT\r\n", 6);
1102 ml_sock_gets(sock, buf);
1103 lprintf(CTDL_DEBUG, "<%s\n", buf);
1104 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1107 bail: if (msg_fp != NULL) fclose(msg_fp);
1115 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1116 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1117 * a "bounce" message (delivery status notification).
1119 void smtp_do_bounce(char *instr) {
1127 char bounceto[1024];
1128 int num_bounces = 0;
1129 int bounce_this = 0;
1130 long bounce_msgid = (-1);
1131 time_t submitted = 0L;
1132 struct CtdlMessage *bmsg = NULL;
1134 struct recptypes *valid;
1135 int successful_bounce = 0;
1137 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1138 strcpy(bounceto, "");
1140 lines = num_tokens(instr, '\n');
1143 /* See if it's time to give up on delivery of this message */
1144 for (i=0; i<lines; ++i) {
1145 extract_token(buf, instr, i, '\n');
1146 extract(key, buf, 0);
1147 extract(addr, buf, 1);
1148 if (!strcasecmp(key, "submitted")) {
1149 submitted = atol(addr);
1153 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1159 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1160 if (bmsg == NULL) return;
1161 memset(bmsg, 0, sizeof(struct CtdlMessage));
1163 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1164 bmsg->cm_anon_type = MES_NORMAL;
1165 bmsg->cm_format_type = 1;
1166 bmsg->cm_fields['A'] = strdoop("Citadel");
1167 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1168 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1170 if (give_up) bmsg->cm_fields['M'] = strdoop(
1171 "A message you sent could not be delivered to some or all of its recipients\n"
1172 "due to prolonged unavailability of its destination(s).\n"
1173 "Giving up on the following addresses:\n\n"
1176 else bmsg->cm_fields['M'] = strdoop(
1177 "A message you sent could not be delivered to some or all of its recipients.\n"
1178 "The following addresses were undeliverable:\n\n"
1182 * Now go through the instructions checking for stuff.
1184 for (i=0; i<lines; ++i) {
1185 extract_token(buf, instr, i, '\n');
1186 extract(key, buf, 0);
1187 extract(addr, buf, 1);
1188 status = extract_int(buf, 2);
1189 extract(dsn, buf, 3);
1192 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1193 key, addr, status, dsn);
1195 if (!strcasecmp(key, "bounceto")) {
1196 strcpy(bounceto, addr);
1200 (!strcasecmp(key, "local"))
1201 || (!strcasecmp(key, "remote"))
1202 || (!strcasecmp(key, "ignet"))
1203 || (!strcasecmp(key, "room"))
1205 if (status == 5) bounce_this = 1;
1206 if (give_up) bounce_this = 1;
1212 if (bmsg->cm_fields['M'] == NULL) {
1213 lprintf(CTDL_ERR, "ERROR ... M field is null "
1214 "(%s:%d)\n", __FILE__, __LINE__);
1217 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1218 strlen(bmsg->cm_fields['M']) + 1024 );
1219 strcat(bmsg->cm_fields['M'], addr);
1220 strcat(bmsg->cm_fields['M'], ": ");
1221 strcat(bmsg->cm_fields['M'], dsn);
1222 strcat(bmsg->cm_fields['M'], "\n");
1224 remove_token(instr, i, '\n');
1230 /* Deliver the bounce if there's anything worth mentioning */
1231 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1232 if (num_bounces > 0) {
1234 /* First try the user who sent the message */
1235 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1236 if (strlen(bounceto) == 0) {
1237 lprintf(CTDL_ERR, "No bounce address specified\n");
1238 bounce_msgid = (-1L);
1241 /* Can we deliver the bounce to the original sender? */
1242 valid = validate_recipients(bounceto);
1243 if (valid != NULL) {
1244 if (valid->num_error == 0) {
1245 CtdlSubmitMsg(bmsg, valid, "");
1246 successful_bounce = 1;
1250 /* If not, post it in the Aide> room */
1251 if (successful_bounce == 0) {
1252 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1255 /* Free up the memory we used */
1256 if (valid != NULL) {
1261 CtdlFreeMessage(bmsg);
1262 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1267 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1268 * set of delivery instructions for completed deliveries and remove them.
1270 * It returns the number of incomplete deliveries remaining.
1272 int smtp_purge_completed_deliveries(char *instr) {
1283 lines = num_tokens(instr, '\n');
1284 for (i=0; i<lines; ++i) {
1285 extract_token(buf, instr, i, '\n');
1286 extract(key, buf, 0);
1287 extract(addr, buf, 1);
1288 status = extract_int(buf, 2);
1289 extract(dsn, buf, 3);
1294 (!strcasecmp(key, "local"))
1295 || (!strcasecmp(key, "remote"))
1296 || (!strcasecmp(key, "ignet"))
1297 || (!strcasecmp(key, "room"))
1299 if (status == 2) completed = 1;
1304 remove_token(instr, i, '\n');
1317 * Called by smtp_do_queue() to handle an individual message.
1319 void smtp_do_procmsg(long msgnum, void *userdata) {
1320 struct CtdlMessage *msg;
1322 char *results = NULL;
1330 long text_msgid = (-1);
1331 int incomplete_deliveries_remaining;
1332 time_t attempted = 0L;
1333 time_t last_attempted = 0L;
1334 time_t retry = SMTP_RETRY_INTERVAL;
1336 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1338 msg = CtdlFetchMessage(msgnum);
1340 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1344 instr = strdoop(msg->cm_fields['M']);
1345 CtdlFreeMessage(msg);
1347 /* Strip out the headers amd any other non-instruction line */
1348 lines = num_tokens(instr, '\n');
1349 for (i=0; i<lines; ++i) {
1350 extract_token(buf, instr, i, '\n');
1351 if (num_tokens(buf, '|') < 2) {
1352 remove_token(instr, i, '\n');
1358 /* Learn the message ID and find out about recent delivery attempts */
1359 lines = num_tokens(instr, '\n');
1360 for (i=0; i<lines; ++i) {
1361 extract_token(buf, instr, i, '\n');
1362 extract(key, buf, 0);
1363 if (!strcasecmp(key, "msgid")) {
1364 text_msgid = extract_long(buf, 1);
1366 if (!strcasecmp(key, "retry")) {
1367 /* double the retry interval after each attempt */
1368 retry = extract_long(buf, 1) * 2L;
1369 if (retry > SMTP_RETRY_MAX) {
1370 retry = SMTP_RETRY_MAX;
1372 remove_token(instr, i, '\n');
1374 if (!strcasecmp(key, "attempted")) {
1375 attempted = extract_long(buf, 1);
1376 if (attempted > last_attempted)
1377 last_attempted = attempted;
1382 * Postpone delivery if we've already tried recently.
1384 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1385 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1392 * Bail out if there's no actual message associated with this
1394 if (text_msgid < 0L) {
1395 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1400 /* Plow through the instructions looking for 'remote' directives and
1401 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1402 * were experienced and it's time to try again)
1404 lines = num_tokens(instr, '\n');
1405 for (i=0; i<lines; ++i) {
1406 extract_token(buf, instr, i, '\n');
1407 extract(key, buf, 0);
1408 extract(addr, buf, 1);
1409 status = extract_int(buf, 2);
1410 extract(dsn, buf, 3);
1411 if ( (!strcasecmp(key, "remote"))
1412 && ((status==0)||(status==3)||(status==4)) ) {
1414 /* Remove this "remote" instruction from the set,
1415 * but replace the set's final newline if
1416 * remove_token() stripped it. It has to be there.
1418 remove_token(instr, i, '\n');
1419 if (instr[strlen(instr)-1] != '\n') {
1420 strcat(instr, "\n");
1425 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1426 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1428 if (results == NULL) {
1429 results = mallok(1024);
1430 memset(results, 0, 1024);
1433 results = reallok(results,
1434 strlen(results) + 1024);
1436 snprintf(&results[strlen(results)], 1024,
1438 key, addr, status, dsn);
1443 if (results != NULL) {
1444 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1445 strcat(instr, results);
1450 /* Generate 'bounce' messages */
1451 smtp_do_bounce(instr);
1453 /* Go through the delivery list, deleting completed deliveries */
1454 incomplete_deliveries_remaining =
1455 smtp_purge_completed_deliveries(instr);
1459 * No delivery instructions remain, so delete both the instructions
1460 * message and the message message.
1462 if (incomplete_deliveries_remaining <= 0) {
1463 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1464 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1469 * Uncompleted delivery instructions remain, so delete the old
1470 * instructions and replace with the updated ones.
1472 if (incomplete_deliveries_remaining > 0) {
1473 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1474 msg = mallok(sizeof(struct CtdlMessage));
1475 memset(msg, 0, sizeof(struct CtdlMessage));
1476 msg->cm_magic = CTDLMESSAGE_MAGIC;
1477 msg->cm_anon_type = MES_NORMAL;
1478 msg->cm_format_type = FMT_RFC822;
1479 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1480 snprintf(msg->cm_fields['M'],
1482 "Content-type: %s\n\n%s\n"
1485 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1487 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1488 CtdlFreeMessage(msg);
1498 * Run through the queue sending out messages.
1500 void smtp_do_queue(void) {
1501 static int doing_queue = 0;
1504 * This is a simple concurrency check to make sure only one queue run
1505 * is done at a time. We could do this with a mutex, but since we
1506 * don't really require extremely fine granularity here, we'll do it
1507 * with a static variable instead.
1509 if (doing_queue) return;
1513 * Go ahead and run the queue
1515 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1517 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1518 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1521 CtdlForEachMessage(MSGS_ALL, 0L,
1522 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1524 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1531 /*****************************************************************************/
1532 /* SMTP UTILITY COMMANDS */
1533 /*****************************************************************************/
1535 void cmd_smtp(char *argbuf) {
1542 if (CtdlAccessCheck(ac_aide)) return;
1544 extract(cmd, argbuf, 0);
1546 if (!strcasecmp(cmd, "mx")) {
1547 extract(node, argbuf, 1);
1548 num_mxhosts = getmx(buf, node);
1549 cprintf("%d %d MX hosts listed for %s\n",
1550 LISTING_FOLLOWS, num_mxhosts, node);
1551 for (i=0; i<num_mxhosts; ++i) {
1552 extract(node, buf, i);
1553 cprintf("%s\n", node);
1559 else if (!strcasecmp(cmd, "runqueue")) {
1561 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1566 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1573 * Initialize the SMTP outbound queue
1575 void smtp_init_spoolout(void) {
1576 struct ctdlroom qrbuf;
1579 * Create the room. This will silently fail if the room already
1580 * exists, and that's perfectly ok, because we want it to exist.
1582 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0);
1585 * Make sure it's set to be a "system room" so it doesn't show up
1586 * in the <K>nown rooms list for Aides.
1588 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1589 qrbuf.QRflags2 |= QR2_SYSTEM;
1597 /*****************************************************************************/
1598 /* MODULE INITIALIZATION STUFF */
1599 /*****************************************************************************/
1602 char *serv_smtp_init(void)
1604 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1609 CtdlRegisterServiceHook(0, /* ...and locally */
1614 smtp_init_spoolout();
1615 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1616 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");