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 /* PIPELINING and STARTTLS are mutually exclusive. */
202 if (!CC->redirect_ssl) {
203 cprintf("250-PIPELINING\r\n");
206 cprintf("250-AUTH LOGIN PLAIN\r\n");
207 cprintf("250-AUTH=LOGIN PLAIN\r\n");
209 cprintf("250-STARTTLS\r\n");
211 cprintf("250 ENHANCEDSTATUSCODES\r\n");
218 * Implement HELP command.
220 void smtp_help(void) {
221 cprintf("214-Commands accepted:\r\n");
222 cprintf("214- DATA\r\n");
223 cprintf("214- EHLO\r\n");
224 cprintf("214- EXPN\r\n");
225 cprintf("214- HELO\r\n");
226 cprintf("214- HELP\r\n");
227 cprintf("214- MAIL\r\n");
228 cprintf("214- NOOP\r\n");
229 cprintf("214- QUIT\r\n");
230 cprintf("214- RCPT\r\n");
231 cprintf("214- RSET\r\n");
232 cprintf("214- VRFY\r\n");
240 void smtp_get_user(char *argbuf) {
244 CtdlDecodeBase64(username, argbuf, SIZ);
245 lprintf(CTDL_DEBUG, "Trying <%s>\n", username);
246 if (CtdlLoginExistingUser(username) == login_ok) {
247 CtdlEncodeBase64(buf, "Password:", 9);
248 cprintf("334 %s\r\n", buf);
249 SMTP->command_state = smtp_password;
252 cprintf("500 5.7.0 No such user.\r\n");
253 SMTP->command_state = smtp_command;
261 void smtp_get_pass(char *argbuf) {
264 CtdlDecodeBase64(password, argbuf, SIZ);
265 lprintf(CTDL_DEBUG, "Trying <%s>\n", password);
266 if (CtdlTryPassword(password) == pass_ok) {
267 smtp_auth_greeting();
270 cprintf("535 5.7.0 Authentication failed.\r\n");
272 SMTP->command_state = smtp_command;
279 void smtp_auth(char *argbuf) {
282 char encoded_authstring[SIZ];
283 char decoded_authstring[SIZ];
289 cprintf("504 5.7.4 Already logged in.\r\n");
293 extract_token(method, argbuf, 0, ' ');
295 if (!strncasecmp(method, "login", 5) ) {
296 if (strlen(argbuf) >= 7) {
297 smtp_get_user(&argbuf[6]);
300 CtdlEncodeBase64(buf, "Username:", 9);
301 cprintf("334 %s\r\n", buf);
302 SMTP->command_state = smtp_user;
307 if (!strncasecmp(method, "plain", 5) ) {
308 extract_token(encoded_authstring, argbuf, 1, ' ');
309 CtdlDecodeBase64(decoded_authstring,
311 strlen(encoded_authstring) );
312 strcpy(ident, decoded_authstring);
313 strcpy(user, &decoded_authstring[strlen(ident) + 1] );
314 strcpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2] );
316 if (CtdlLoginExistingUser(user) == login_ok) {
317 if (CtdlTryPassword(pass) == pass_ok) {
318 smtp_auth_greeting();
322 cprintf("504 5.7.4 Authentication failed.\r\n");
325 if (strncasecmp(method, "login", 5) ) {
326 cprintf("504 5.7.4 Unknown authentication method.\r\n");
334 * Back end for smtp_vrfy() command
336 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
338 if (!fuzzy_match(us, SMTP->vrfy_match)) {
340 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
346 * Implements the VRFY (verify user name) command.
347 * Performs fuzzy match on full user names.
349 void smtp_vrfy(char *argbuf) {
350 SMTP->vrfy_count = 0;
351 strcpy(SMTP->vrfy_match, argbuf);
352 ForEachUser(smtp_vrfy_backend, NULL);
354 if (SMTP->vrfy_count < 1) {
355 cprintf("550 5.1.1 String does not match anything.\r\n");
357 else if (SMTP->vrfy_count == 1) {
358 cprintf("250 %s <cit%ld@%s>\r\n",
359 SMTP->vrfy_buffer.fullname,
360 SMTP->vrfy_buffer.usernum,
363 else if (SMTP->vrfy_count > 1) {
364 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
373 * Back end for smtp_expn() command
375 void smtp_expn_backend(struct ctdluser *us, void *data) {
377 if (!fuzzy_match(us, SMTP->vrfy_match)) {
379 if (SMTP->vrfy_count >= 1) {
380 cprintf("250-%s <cit%ld@%s>\r\n",
381 SMTP->vrfy_buffer.fullname,
382 SMTP->vrfy_buffer.usernum,
387 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
393 * Implements the EXPN (expand user name) command.
394 * Performs fuzzy match on full user names.
396 void smtp_expn(char *argbuf) {
397 SMTP->vrfy_count = 0;
398 strcpy(SMTP->vrfy_match, argbuf);
399 ForEachUser(smtp_expn_backend, NULL);
401 if (SMTP->vrfy_count < 1) {
402 cprintf("550 5.1.1 String does not match anything.\r\n");
404 else if (SMTP->vrfy_count >= 1) {
405 cprintf("250 %s <cit%ld@%s>\r\n",
406 SMTP->vrfy_buffer.fullname,
407 SMTP->vrfy_buffer.usernum,
414 * Implements the RSET (reset state) command.
415 * Currently this just zeroes out the state buffer. If pointers to data
416 * allocated with mallok() are ever placed in the state buffer, we have to
417 * be sure to phree() them first!
419 * Set do_response to nonzero to output the SMTP RSET response code.
421 void smtp_rset(int do_response) {
425 * Our entire SMTP state is discarded when a RSET command is issued,
426 * but we need to preserve this one little piece of information, so
427 * we save it for later.
429 is_lmtp = SMTP->is_lmtp;
431 memset(SMTP, 0, sizeof(struct citsmtp));
434 * It is somewhat ambiguous whether we want to log out when a RSET
435 * command is issued. Here's the code to do it. It is commented out
436 * because some clients (such as Pine) issue RSET commands before
437 * each message, but still expect to be logged in.
439 * if (CC->logged_in) {
445 * Reinstate this little piece of information we saved (see above).
447 SMTP->is_lmtp = is_lmtp;
450 cprintf("250 2.0.0 Zap!\r\n");
455 * Clear out the portions of the state buffer that need to be cleared out
456 * after the DATA command finishes.
458 void smtp_data_clear(void) {
459 strcpy(SMTP->from, "");
460 strcpy(SMTP->recipients, "");
461 SMTP->number_of_recipients = 0;
462 SMTP->delivery_mode = 0;
463 SMTP->message_originated_locally = 0;
469 * Implements the "MAIL From:" command
471 void smtp_mail(char *argbuf) {
476 if (strlen(SMTP->from) != 0) {
477 cprintf("503 5.1.0 Only one sender permitted\r\n");
481 if (strncasecmp(argbuf, "From:", 5)) {
482 cprintf("501 5.1.7 Syntax error\r\n");
486 strcpy(SMTP->from, &argbuf[5]);
488 stripallbut(SMTP->from, '<', '>');
490 /* We used to reject empty sender names, until it was brought to our
491 * attention that RFC1123 5.2.9 requires that this be allowed. So now
492 * we allow it, but replace the empty string with a fake
493 * address so we don't have to contend with the empty string causing
494 * other code to fail when it's expecting something there.
496 if (strlen(SMTP->from) == 0) {
497 strcpy(SMTP->from, "someone@somewhere.org");
500 /* If this SMTP connection is from a logged-in user, force the 'from'
501 * to be the user's Internet e-mail address as Citadel knows it.
504 strcpy(SMTP->from, CC->cs_inet_email);
505 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
506 SMTP->message_originated_locally = 1;
510 else if (SMTP->is_lmtp) {
511 /* Bypass forgery checking for LMTP */
514 /* Otherwise, make sure outsiders aren't trying to forge mail from
518 process_rfc822_addr(SMTP->from, user, node, name);
519 if (CtdlHostAlias(node) != hostalias_nomatch) {
521 "You must log in to send mail from %s\r\n",
523 strcpy(SMTP->from, "");
528 cprintf("250 2.0.0 Sender ok\r\n");
534 * Implements the "RCPT To:" command
536 void smtp_rcpt(char *argbuf) {
538 char message_to_spammer[SIZ];
539 struct recptypes *valid = NULL;
541 if (strlen(SMTP->from) == 0) {
542 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
546 if (strncasecmp(argbuf, "To:", 3)) {
547 cprintf("501 5.1.7 Syntax error\r\n");
551 strcpy(recp, &argbuf[3]);
553 stripallbut(recp, '<', '>');
555 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
556 cprintf("452 4.5.3 Too many recipients\r\n");
561 if ( (!CC->logged_in)
562 && (!SMTP->is_lmtp) ) {
563 if (rbl_check(message_to_spammer)) {
564 cprintf("550 %s\r\n", message_to_spammer);
565 /* no need to phree(valid), it's not allocated yet */
570 valid = validate_recipients(recp);
571 if (valid->num_error > 0) {
572 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
577 if (valid->num_internet > 0) {
578 if ( (SMTP->message_originated_locally == 0)
579 && (SMTP->is_lmtp == 0) ) {
580 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
586 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
587 if (strlen(SMTP->recipients) > 0) {
588 strcat(SMTP->recipients, ",");
590 strcat(SMTP->recipients, recp);
591 SMTP->number_of_recipients += 1;
598 * Implements the DATA command
600 void smtp_data(void) {
602 struct CtdlMessage *msg;
605 struct recptypes *valid;
610 if (strlen(SMTP->from) == 0) {
611 cprintf("503 5.5.1 Need MAIL command first.\r\n");
615 if (SMTP->number_of_recipients < 1) {
616 cprintf("503 5.5.1 Need RCPT command first.\r\n");
620 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
622 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
625 if (body != NULL) snprintf(body, 4096,
626 "Received: from %s (%s [%s])\n"
634 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
637 "Unable to save message: internal error.\r\n");
641 lprintf(CTDL_DEBUG, "Converting message...\n");
642 msg = convert_internet_message(body);
644 /* If the user is locally authenticated, FORCE the From: header to
645 * show up as the real sender. Yes, this violates the RFC standard,
646 * but IT MAKES SENSE. If you prefer strict RFC adherence over
647 * common sense, you can disable this in the configuration.
649 * We also set the "message room name" ('O' field) to MAILROOM
650 * (which is Mail> on most systems) to prevent it from getting set
651 * to something ugly like "0000058008.Sent Items>" when the message
652 * is read with a Citadel client.
654 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
655 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
656 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
657 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
658 if (msg->cm_fields['F'] != NULL) phree(msg->cm_fields['F']);
659 if (msg->cm_fields['O'] != NULL) phree(msg->cm_fields['O']);
660 msg->cm_fields['A'] = strdoop(CC->user.fullname);
661 msg->cm_fields['N'] = strdoop(config.c_nodename);
662 msg->cm_fields['H'] = strdoop(config.c_humannode);
663 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
664 msg->cm_fields['O'] = strdoop(MAILROOM);
667 /* Submit the message into the Citadel system. */
668 valid = validate_recipients(SMTP->recipients);
670 /* If there are modules that want to scan this message before final
671 * submission (such as virus checkers or spam filters), call them now
672 * and give them an opportunity to reject the message.
674 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
676 if (scan_errors > 0) { /* We don't want this message! */
678 if (msg->cm_fields['0'] == NULL) {
679 msg->cm_fields['0'] = strdoop(
680 "5.7.1 Message rejected by filter");
683 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
686 else { /* Ok, we'll accept this message. */
687 msgnum = CtdlSubmitMsg(msg, valid, "");
689 sprintf(result, "250 2.0.0 Message accepted.\r\n");
692 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
696 /* For SMTP and ESTMP, just print the result message. For LMTP, we
697 * have to print one result message for each recipient. Since there
698 * is nothing in Citadel which would cause different recipients to
699 * have different results, we can get away with just spitting out the
700 * same message once for each recipient.
703 for (i=0; i<SMTP->number_of_recipients; ++i) {
704 cprintf("%s", result);
708 cprintf("%s", result);
711 CtdlFreeMessage(msg);
713 smtp_data_clear(); /* clear out the buffers now */
718 * implements the STARTTLS command (Citadel API version)
721 void smtp_starttls(void)
723 char ok_response[SIZ];
724 char nosup_response[SIZ];
725 char error_response[SIZ];
728 "200 2.0.0 Begin TLS negotiation now\r\n");
729 sprintf(nosup_response,
730 "554 5.7.3 TLS not supported here\r\n");
731 sprintf(error_response,
732 "554 5.7.3 Internal error\r\n");
733 CtdlStartTLS(ok_response, nosup_response, error_response);
741 * Main command loop for SMTP sessions.
743 void smtp_command_loop(void) {
746 lprintf(CTDL_DEBUG, "Start of SMTP command loop\n");
748 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
749 lprintf(CTDL_DEBUG, "Awaiting SMTP command...\n");
750 if (client_gets(cmdbuf) < 1) {
751 lprintf(CTDL_CRIT, "SMTP socket is broken. Ending session.\n");
755 lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
756 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
758 if (SMTP->command_state == smtp_user) {
759 smtp_get_user(cmdbuf);
762 else if (SMTP->command_state == smtp_password) {
763 smtp_get_pass(cmdbuf);
766 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
767 smtp_auth(&cmdbuf[5]);
770 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
774 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
775 smtp_expn(&cmdbuf[5]);
778 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
779 smtp_hello(&cmdbuf[5], 0);
782 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
783 smtp_hello(&cmdbuf[5], 1);
786 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
787 smtp_hello(&cmdbuf[5], 2);
790 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
794 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
795 lprintf(CTDL_DEBUG, "Performing MAIL command\n");
796 smtp_mail(&cmdbuf[5]);
797 lprintf(CTDL_DEBUG, "Finished MAIL command\n");
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");
830 lprintf(CTDL_DEBUG, "End of SMTP command loop\n");
837 /*****************************************************************************/
838 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
839 /*****************************************************************************/
846 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
849 void smtp_try(const char *key, const char *addr, int *status,
850 char *dsn, size_t n, long msgnum)
857 char user[SIZ], node[SIZ], name[SIZ];
863 size_t blocksize = 0;
866 /* Parse out the host portion of the recipient address */
867 process_rfc822_addr(addr, user, node, name);
869 lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
872 /* Load the message out of the database into a temp file */
874 if (msg_fp == NULL) {
876 snprintf(dsn, n, "Error creating temporary file");
880 CtdlRedirectOutput(msg_fp, -1);
881 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
882 CtdlRedirectOutput(NULL, -1);
883 fseek(msg_fp, 0L, SEEK_END);
884 msg_size = ftell(msg_fp);
888 /* Extract something to send later in the 'MAIL From:' command */
889 strcpy(mailfrom, "");
893 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
894 if (!strncasecmp(buf, "From:", 5)) {
895 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
897 for (i=0; i<strlen(mailfrom); ++i) {
898 if (!isprint(mailfrom[i])) {
899 strcpy(&mailfrom[i], &mailfrom[i+1]);
904 /* Strip out parenthesized 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)) {
912 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
915 /* Prefer brokketized names */
918 for (i=0; i<strlen(mailfrom); ++i) {
919 if (mailfrom[i] == '<') lp = i;
920 if (mailfrom[i] == '>') rp = i;
922 if ( (lp>=0) && (rp>lp) ) {
924 strcpy(mailfrom, &mailfrom[lp]);
929 } while (scan_done == 0);
930 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
932 /* Figure out what mail exchanger host we have to connect to */
933 num_mxhosts = getmx(mxhosts, node);
934 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
935 if (num_mxhosts < 1) {
937 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
942 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
943 extract(buf, mxhosts, mx);
944 lprintf(CTDL_DEBUG, "Trying <%s>\n", buf);
945 sock = sock_connect(buf, "25", "tcp");
946 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
947 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
948 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
952 *status = 4; /* dsn is already filled in */
956 /* Process the SMTP greeting from the server */
957 if (ml_sock_gets(sock, buf) < 0) {
959 strcpy(dsn, "Connection broken during SMTP conversation");
962 lprintf(CTDL_DEBUG, "<%s\n", buf);
966 safestrncpy(dsn, &buf[4], 1023);
971 safestrncpy(dsn, &buf[4], 1023);
976 /* At this point we know we are talking to a real SMTP server */
978 /* Do a HELO command */
979 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
980 lprintf(CTDL_DEBUG, ">%s", buf);
981 sock_write(sock, buf, strlen(buf));
982 if (ml_sock_gets(sock, buf) < 0) {
984 strcpy(dsn, "Connection broken during SMTP HELO");
987 lprintf(CTDL_DEBUG, "<%s\n", buf);
991 safestrncpy(dsn, &buf[4], 1023);
996 safestrncpy(dsn, &buf[4], 1023);
1002 /* HELO succeeded, now try the MAIL From: command */
1003 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1004 lprintf(CTDL_DEBUG, ">%s", buf);
1005 sock_write(sock, buf, strlen(buf));
1006 if (ml_sock_gets(sock, buf) < 0) {
1008 strcpy(dsn, "Connection broken during SMTP MAIL");
1011 lprintf(CTDL_DEBUG, "<%s\n", buf);
1012 if (buf[0] != '2') {
1013 if (buf[0] == '4') {
1015 safestrncpy(dsn, &buf[4], 1023);
1020 safestrncpy(dsn, &buf[4], 1023);
1026 /* MAIL succeeded, now try the RCPT To: command */
1027 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
1028 lprintf(CTDL_DEBUG, ">%s", buf);
1029 sock_write(sock, buf, strlen(buf));
1030 if (ml_sock_gets(sock, buf) < 0) {
1032 strcpy(dsn, "Connection broken during SMTP RCPT");
1035 lprintf(CTDL_DEBUG, "<%s\n", buf);
1036 if (buf[0] != '2') {
1037 if (buf[0] == '4') {
1039 safestrncpy(dsn, &buf[4], 1023);
1044 safestrncpy(dsn, &buf[4], 1023);
1050 /* RCPT succeeded, now try the DATA command */
1051 lprintf(CTDL_DEBUG, ">DATA\n");
1052 sock_write(sock, "DATA\r\n", 6);
1053 if (ml_sock_gets(sock, buf) < 0) {
1055 strcpy(dsn, "Connection broken during SMTP DATA");
1058 lprintf(CTDL_DEBUG, "<%s\n", buf);
1059 if (buf[0] != '3') {
1060 if (buf[0] == '4') {
1062 safestrncpy(dsn, &buf[4], 1023);
1067 safestrncpy(dsn, &buf[4], 1023);
1072 /* If we reach this point, the server is expecting data */
1074 while (msg_size > 0) {
1075 blocksize = sizeof(buf);
1076 if (blocksize > msg_size) blocksize = msg_size;
1077 fread(buf, blocksize, 1, msg_fp);
1078 sock_write(sock, buf, blocksize);
1079 msg_size -= blocksize;
1081 if (buf[blocksize-1] != 10) {
1082 lprintf(CTDL_WARNING, "Possible problem: message did not "
1083 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1087 sock_write(sock, ".\r\n", 3);
1088 if (ml_sock_gets(sock, buf) < 0) {
1090 strcpy(dsn, "Connection broken during SMTP message transmit");
1093 lprintf(CTDL_DEBUG, "%s\n", buf);
1094 if (buf[0] != '2') {
1095 if (buf[0] == '4') {
1097 safestrncpy(dsn, &buf[4], 1023);
1102 safestrncpy(dsn, &buf[4], 1023);
1108 safestrncpy(dsn, &buf[4], 1023);
1111 lprintf(CTDL_DEBUG, ">QUIT\n");
1112 sock_write(sock, "QUIT\r\n", 6);
1113 ml_sock_gets(sock, buf);
1114 lprintf(CTDL_DEBUG, "<%s\n", buf);
1115 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1118 bail: if (msg_fp != NULL) fclose(msg_fp);
1126 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1127 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1128 * a "bounce" message (delivery status notification).
1130 void smtp_do_bounce(char *instr) {
1138 char bounceto[1024];
1139 int num_bounces = 0;
1140 int bounce_this = 0;
1141 long bounce_msgid = (-1);
1142 time_t submitted = 0L;
1143 struct CtdlMessage *bmsg = NULL;
1145 struct recptypes *valid;
1146 int successful_bounce = 0;
1148 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1149 strcpy(bounceto, "");
1151 lines = num_tokens(instr, '\n');
1154 /* See if it's time to give up on delivery of this message */
1155 for (i=0; i<lines; ++i) {
1156 extract_token(buf, instr, i, '\n');
1157 extract(key, buf, 0);
1158 extract(addr, buf, 1);
1159 if (!strcasecmp(key, "submitted")) {
1160 submitted = atol(addr);
1164 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1170 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1171 if (bmsg == NULL) return;
1172 memset(bmsg, 0, sizeof(struct CtdlMessage));
1174 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1175 bmsg->cm_anon_type = MES_NORMAL;
1176 bmsg->cm_format_type = 1;
1177 bmsg->cm_fields['A'] = strdoop("Citadel");
1178 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1179 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1181 if (give_up) bmsg->cm_fields['M'] = strdoop(
1182 "A message you sent could not be delivered to some or all of its recipients\n"
1183 "due to prolonged unavailability of its destination(s).\n"
1184 "Giving up on the following addresses:\n\n"
1187 else bmsg->cm_fields['M'] = strdoop(
1188 "A message you sent could not be delivered to some or all of its recipients.\n"
1189 "The following addresses were undeliverable:\n\n"
1193 * Now go through the instructions checking for stuff.
1195 for (i=0; i<lines; ++i) {
1196 extract_token(buf, instr, i, '\n');
1197 extract(key, buf, 0);
1198 extract(addr, buf, 1);
1199 status = extract_int(buf, 2);
1200 extract(dsn, buf, 3);
1203 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1204 key, addr, status, dsn);
1206 if (!strcasecmp(key, "bounceto")) {
1207 strcpy(bounceto, addr);
1211 (!strcasecmp(key, "local"))
1212 || (!strcasecmp(key, "remote"))
1213 || (!strcasecmp(key, "ignet"))
1214 || (!strcasecmp(key, "room"))
1216 if (status == 5) bounce_this = 1;
1217 if (give_up) bounce_this = 1;
1223 if (bmsg->cm_fields['M'] == NULL) {
1224 lprintf(CTDL_ERR, "ERROR ... M field is null "
1225 "(%s:%d)\n", __FILE__, __LINE__);
1228 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1229 strlen(bmsg->cm_fields['M']) + 1024 );
1230 strcat(bmsg->cm_fields['M'], addr);
1231 strcat(bmsg->cm_fields['M'], ": ");
1232 strcat(bmsg->cm_fields['M'], dsn);
1233 strcat(bmsg->cm_fields['M'], "\n");
1235 remove_token(instr, i, '\n');
1241 /* Deliver the bounce if there's anything worth mentioning */
1242 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1243 if (num_bounces > 0) {
1245 /* First try the user who sent the message */
1246 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1247 if (strlen(bounceto) == 0) {
1248 lprintf(CTDL_ERR, "No bounce address specified\n");
1249 bounce_msgid = (-1L);
1252 /* Can we deliver the bounce to the original sender? */
1253 valid = validate_recipients(bounceto);
1254 if (valid != NULL) {
1255 if (valid->num_error == 0) {
1256 CtdlSubmitMsg(bmsg, valid, "");
1257 successful_bounce = 1;
1261 /* If not, post it in the Aide> room */
1262 if (successful_bounce == 0) {
1263 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1266 /* Free up the memory we used */
1267 if (valid != NULL) {
1272 CtdlFreeMessage(bmsg);
1273 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1278 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1279 * set of delivery instructions for completed deliveries and remove them.
1281 * It returns the number of incomplete deliveries remaining.
1283 int smtp_purge_completed_deliveries(char *instr) {
1294 lines = num_tokens(instr, '\n');
1295 for (i=0; i<lines; ++i) {
1296 extract_token(buf, instr, i, '\n');
1297 extract(key, buf, 0);
1298 extract(addr, buf, 1);
1299 status = extract_int(buf, 2);
1300 extract(dsn, buf, 3);
1305 (!strcasecmp(key, "local"))
1306 || (!strcasecmp(key, "remote"))
1307 || (!strcasecmp(key, "ignet"))
1308 || (!strcasecmp(key, "room"))
1310 if (status == 2) completed = 1;
1315 remove_token(instr, i, '\n');
1328 * Called by smtp_do_queue() to handle an individual message.
1330 void smtp_do_procmsg(long msgnum, void *userdata) {
1331 struct CtdlMessage *msg;
1333 char *results = NULL;
1341 long text_msgid = (-1);
1342 int incomplete_deliveries_remaining;
1343 time_t attempted = 0L;
1344 time_t last_attempted = 0L;
1345 time_t retry = SMTP_RETRY_INTERVAL;
1347 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1349 msg = CtdlFetchMessage(msgnum);
1351 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1355 instr = strdoop(msg->cm_fields['M']);
1356 CtdlFreeMessage(msg);
1358 /* Strip out the headers amd any other non-instruction line */
1359 lines = num_tokens(instr, '\n');
1360 for (i=0; i<lines; ++i) {
1361 extract_token(buf, instr, i, '\n');
1362 if (num_tokens(buf, '|') < 2) {
1363 remove_token(instr, i, '\n');
1369 /* Learn the message ID and find out about recent delivery attempts */
1370 lines = num_tokens(instr, '\n');
1371 for (i=0; i<lines; ++i) {
1372 extract_token(buf, instr, i, '\n');
1373 extract(key, buf, 0);
1374 if (!strcasecmp(key, "msgid")) {
1375 text_msgid = extract_long(buf, 1);
1377 if (!strcasecmp(key, "retry")) {
1378 /* double the retry interval after each attempt */
1379 retry = extract_long(buf, 1) * 2L;
1380 if (retry > SMTP_RETRY_MAX) {
1381 retry = SMTP_RETRY_MAX;
1383 remove_token(instr, i, '\n');
1385 if (!strcasecmp(key, "attempted")) {
1386 attempted = extract_long(buf, 1);
1387 if (attempted > last_attempted)
1388 last_attempted = attempted;
1393 * Postpone delivery if we've already tried recently.
1395 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1396 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1403 * Bail out if there's no actual message associated with this
1405 if (text_msgid < 0L) {
1406 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1411 /* Plow through the instructions looking for 'remote' directives and
1412 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1413 * were experienced and it's time to try again)
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 extract(addr, buf, 1);
1420 status = extract_int(buf, 2);
1421 extract(dsn, buf, 3);
1422 if ( (!strcasecmp(key, "remote"))
1423 && ((status==0)||(status==3)||(status==4)) ) {
1425 /* Remove this "remote" instruction from the set,
1426 * but replace the set's final newline if
1427 * remove_token() stripped it. It has to be there.
1429 remove_token(instr, i, '\n');
1430 if (instr[strlen(instr)-1] != '\n') {
1431 strcat(instr, "\n");
1436 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1437 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1439 if (results == NULL) {
1440 results = mallok(1024);
1441 memset(results, 0, 1024);
1444 results = reallok(results,
1445 strlen(results) + 1024);
1447 snprintf(&results[strlen(results)], 1024,
1449 key, addr, status, dsn);
1454 if (results != NULL) {
1455 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1456 strcat(instr, results);
1461 /* Generate 'bounce' messages */
1462 smtp_do_bounce(instr);
1464 /* Go through the delivery list, deleting completed deliveries */
1465 incomplete_deliveries_remaining =
1466 smtp_purge_completed_deliveries(instr);
1470 * No delivery instructions remain, so delete both the instructions
1471 * message and the message message.
1473 if (incomplete_deliveries_remaining <= 0) {
1474 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1475 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1480 * Uncompleted delivery instructions remain, so delete the old
1481 * instructions and replace with the updated ones.
1483 if (incomplete_deliveries_remaining > 0) {
1484 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1485 msg = mallok(sizeof(struct CtdlMessage));
1486 memset(msg, 0, sizeof(struct CtdlMessage));
1487 msg->cm_magic = CTDLMESSAGE_MAGIC;
1488 msg->cm_anon_type = MES_NORMAL;
1489 msg->cm_format_type = FMT_RFC822;
1490 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1491 snprintf(msg->cm_fields['M'],
1493 "Content-type: %s\n\n%s\n"
1496 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1498 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1499 CtdlFreeMessage(msg);
1509 * Run through the queue sending out messages.
1511 void smtp_do_queue(void) {
1512 static int doing_queue = 0;
1515 * This is a simple concurrency check to make sure only one queue run
1516 * is done at a time. We could do this with a mutex, but since we
1517 * don't really require extremely fine granularity here, we'll do it
1518 * with a static variable instead.
1520 if (doing_queue) return;
1524 * Go ahead and run the queue
1526 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1528 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1529 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1532 CtdlForEachMessage(MSGS_ALL, 0L,
1533 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1535 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1542 /*****************************************************************************/
1543 /* SMTP UTILITY COMMANDS */
1544 /*****************************************************************************/
1546 void cmd_smtp(char *argbuf) {
1553 if (CtdlAccessCheck(ac_aide)) return;
1555 extract(cmd, argbuf, 0);
1557 if (!strcasecmp(cmd, "mx")) {
1558 extract(node, argbuf, 1);
1559 num_mxhosts = getmx(buf, node);
1560 cprintf("%d %d MX hosts listed for %s\n",
1561 LISTING_FOLLOWS, num_mxhosts, node);
1562 for (i=0; i<num_mxhosts; ++i) {
1563 extract(node, buf, i);
1564 cprintf("%s\n", node);
1570 else if (!strcasecmp(cmd, "runqueue")) {
1572 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1577 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1584 * Initialize the SMTP outbound queue
1586 void smtp_init_spoolout(void) {
1587 struct ctdlroom qrbuf;
1590 * Create the room. This will silently fail if the room already
1591 * exists, and that's perfectly ok, because we want it to exist.
1593 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0);
1596 * Make sure it's set to be a "system room" so it doesn't show up
1597 * in the <K>nown rooms list for Aides.
1599 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1600 qrbuf.QRflags2 |= QR2_SYSTEM;
1608 /*****************************************************************************/
1609 /* MODULE INITIALIZATION STUFF */
1610 /*****************************************************************************/
1613 char *serv_smtp_init(void)
1615 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1620 CtdlRegisterServiceHook(0, /* ...and locally */
1625 smtp_init_spoolout();
1626 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1627 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");