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) {
747 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
748 if (client_gets(cmdbuf) < 1) {
749 lprintf(CTDL_CRIT, "SMTP socket is broken. Ending session.\n");
753 lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
754 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
756 if (SMTP->command_state == smtp_user) {
757 smtp_get_user(cmdbuf);
760 else if (SMTP->command_state == smtp_password) {
761 smtp_get_pass(cmdbuf);
764 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
765 smtp_auth(&cmdbuf[5]);
768 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
772 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
773 smtp_expn(&cmdbuf[5]);
776 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
777 smtp_hello(&cmdbuf[5], 0);
780 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
781 smtp_hello(&cmdbuf[5], 1);
784 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
785 smtp_hello(&cmdbuf[5], 2);
788 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
792 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
793 smtp_mail(&cmdbuf[5]);
796 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
797 cprintf("250 NOOP\r\n");
800 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
801 cprintf("221 Goodbye...\r\n");
806 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
807 smtp_rcpt(&cmdbuf[5]);
810 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
814 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
818 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
819 smtp_vrfy(&cmdbuf[5]);
823 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
832 /*****************************************************************************/
833 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
834 /*****************************************************************************/
841 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
844 void smtp_try(const char *key, const char *addr, int *status,
845 char *dsn, size_t n, long msgnum)
852 char user[SIZ], node[SIZ], name[SIZ];
858 size_t blocksize = 0;
861 /* Parse out the host portion of the recipient address */
862 process_rfc822_addr(addr, user, node, name);
864 lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
867 /* Load the message out of the database into a temp file */
869 if (msg_fp == NULL) {
871 snprintf(dsn, n, "Error creating temporary file");
875 CtdlRedirectOutput(msg_fp, -1);
876 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
877 CtdlRedirectOutput(NULL, -1);
878 fseek(msg_fp, 0L, SEEK_END);
879 msg_size = ftell(msg_fp);
883 /* Extract something to send later in the 'MAIL From:' command */
884 strcpy(mailfrom, "");
888 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
889 if (!strncasecmp(buf, "From:", 5)) {
890 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
892 for (i=0; i<strlen(mailfrom); ++i) {
893 if (!isprint(mailfrom[i])) {
894 strcpy(&mailfrom[i], &mailfrom[i+1]);
899 /* Strip out parenthesized names */
902 for (i=0; i<strlen(mailfrom); ++i) {
903 if (mailfrom[i] == '(') lp = i;
904 if (mailfrom[i] == ')') rp = i;
906 if ((lp>0)&&(rp>lp)) {
907 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
910 /* Prefer brokketized names */
913 for (i=0; i<strlen(mailfrom); ++i) {
914 if (mailfrom[i] == '<') lp = i;
915 if (mailfrom[i] == '>') rp = i;
917 if ( (lp>=0) && (rp>lp) ) {
919 strcpy(mailfrom, &mailfrom[lp]);
924 } while (scan_done == 0);
925 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
927 /* Figure out what mail exchanger host we have to connect to */
928 num_mxhosts = getmx(mxhosts, node);
929 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
930 if (num_mxhosts < 1) {
932 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
937 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
938 extract(buf, mxhosts, mx);
939 lprintf(CTDL_DEBUG, "Trying <%s>\n", buf);
940 sock = sock_connect(buf, "25", "tcp");
941 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
942 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
943 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
947 *status = 4; /* dsn is already filled in */
951 /* Process the SMTP greeting from the server */
952 if (ml_sock_gets(sock, buf) < 0) {
954 strcpy(dsn, "Connection broken during SMTP conversation");
957 lprintf(CTDL_DEBUG, "<%s\n", buf);
961 safestrncpy(dsn, &buf[4], 1023);
966 safestrncpy(dsn, &buf[4], 1023);
971 /* At this point we know we are talking to a real SMTP server */
973 /* Do a HELO command */
974 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
975 lprintf(CTDL_DEBUG, ">%s", buf);
976 sock_write(sock, buf, strlen(buf));
977 if (ml_sock_gets(sock, buf) < 0) {
979 strcpy(dsn, "Connection broken during SMTP HELO");
982 lprintf(CTDL_DEBUG, "<%s\n", buf);
986 safestrncpy(dsn, &buf[4], 1023);
991 safestrncpy(dsn, &buf[4], 1023);
997 /* HELO succeeded, now try the MAIL From: command */
998 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
999 lprintf(CTDL_DEBUG, ">%s", buf);
1000 sock_write(sock, buf, strlen(buf));
1001 if (ml_sock_gets(sock, buf) < 0) {
1003 strcpy(dsn, "Connection broken during SMTP MAIL");
1006 lprintf(CTDL_DEBUG, "<%s\n", buf);
1007 if (buf[0] != '2') {
1008 if (buf[0] == '4') {
1010 safestrncpy(dsn, &buf[4], 1023);
1015 safestrncpy(dsn, &buf[4], 1023);
1021 /* MAIL succeeded, now try the RCPT To: command */
1022 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
1023 lprintf(CTDL_DEBUG, ">%s", buf);
1024 sock_write(sock, buf, strlen(buf));
1025 if (ml_sock_gets(sock, buf) < 0) {
1027 strcpy(dsn, "Connection broken during SMTP RCPT");
1030 lprintf(CTDL_DEBUG, "<%s\n", buf);
1031 if (buf[0] != '2') {
1032 if (buf[0] == '4') {
1034 safestrncpy(dsn, &buf[4], 1023);
1039 safestrncpy(dsn, &buf[4], 1023);
1045 /* RCPT succeeded, now try the DATA command */
1046 lprintf(CTDL_DEBUG, ">DATA\n");
1047 sock_write(sock, "DATA\r\n", 6);
1048 if (ml_sock_gets(sock, buf) < 0) {
1050 strcpy(dsn, "Connection broken during SMTP DATA");
1053 lprintf(CTDL_DEBUG, "<%s\n", buf);
1054 if (buf[0] != '3') {
1055 if (buf[0] == '4') {
1057 safestrncpy(dsn, &buf[4], 1023);
1062 safestrncpy(dsn, &buf[4], 1023);
1067 /* If we reach this point, the server is expecting data */
1069 while (msg_size > 0) {
1070 blocksize = sizeof(buf);
1071 if (blocksize > msg_size) blocksize = msg_size;
1072 fread(buf, blocksize, 1, msg_fp);
1073 sock_write(sock, buf, blocksize);
1074 msg_size -= blocksize;
1076 if (buf[blocksize-1] != 10) {
1077 lprintf(CTDL_WARNING, "Possible problem: message did not "
1078 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1082 sock_write(sock, ".\r\n", 3);
1083 if (ml_sock_gets(sock, buf) < 0) {
1085 strcpy(dsn, "Connection broken during SMTP message transmit");
1088 lprintf(CTDL_DEBUG, "%s\n", buf);
1089 if (buf[0] != '2') {
1090 if (buf[0] == '4') {
1092 safestrncpy(dsn, &buf[4], 1023);
1097 safestrncpy(dsn, &buf[4], 1023);
1103 safestrncpy(dsn, &buf[4], 1023);
1106 lprintf(CTDL_DEBUG, ">QUIT\n");
1107 sock_write(sock, "QUIT\r\n", 6);
1108 ml_sock_gets(sock, buf);
1109 lprintf(CTDL_DEBUG, "<%s\n", buf);
1110 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1113 bail: if (msg_fp != NULL) fclose(msg_fp);
1121 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1122 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1123 * a "bounce" message (delivery status notification).
1125 void smtp_do_bounce(char *instr) {
1133 char bounceto[1024];
1134 int num_bounces = 0;
1135 int bounce_this = 0;
1136 long bounce_msgid = (-1);
1137 time_t submitted = 0L;
1138 struct CtdlMessage *bmsg = NULL;
1140 struct recptypes *valid;
1141 int successful_bounce = 0;
1143 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1144 strcpy(bounceto, "");
1146 lines = num_tokens(instr, '\n');
1149 /* See if it's time to give up on delivery of this message */
1150 for (i=0; i<lines; ++i) {
1151 extract_token(buf, instr, i, '\n');
1152 extract(key, buf, 0);
1153 extract(addr, buf, 1);
1154 if (!strcasecmp(key, "submitted")) {
1155 submitted = atol(addr);
1159 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1165 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1166 if (bmsg == NULL) return;
1167 memset(bmsg, 0, sizeof(struct CtdlMessage));
1169 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1170 bmsg->cm_anon_type = MES_NORMAL;
1171 bmsg->cm_format_type = 1;
1172 bmsg->cm_fields['A'] = strdoop("Citadel");
1173 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1174 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1176 if (give_up) bmsg->cm_fields['M'] = strdoop(
1177 "A message you sent could not be delivered to some or all of its recipients\n"
1178 "due to prolonged unavailability of its destination(s).\n"
1179 "Giving up on the following addresses:\n\n"
1182 else bmsg->cm_fields['M'] = strdoop(
1183 "A message you sent could not be delivered to some or all of its recipients.\n"
1184 "The following addresses were undeliverable:\n\n"
1188 * Now go through the instructions checking for stuff.
1190 for (i=0; i<lines; ++i) {
1191 extract_token(buf, instr, i, '\n');
1192 extract(key, buf, 0);
1193 extract(addr, buf, 1);
1194 status = extract_int(buf, 2);
1195 extract(dsn, buf, 3);
1198 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1199 key, addr, status, dsn);
1201 if (!strcasecmp(key, "bounceto")) {
1202 strcpy(bounceto, addr);
1206 (!strcasecmp(key, "local"))
1207 || (!strcasecmp(key, "remote"))
1208 || (!strcasecmp(key, "ignet"))
1209 || (!strcasecmp(key, "room"))
1211 if (status == 5) bounce_this = 1;
1212 if (give_up) bounce_this = 1;
1218 if (bmsg->cm_fields['M'] == NULL) {
1219 lprintf(CTDL_ERR, "ERROR ... M field is null "
1220 "(%s:%d)\n", __FILE__, __LINE__);
1223 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1224 strlen(bmsg->cm_fields['M']) + 1024 );
1225 strcat(bmsg->cm_fields['M'], addr);
1226 strcat(bmsg->cm_fields['M'], ": ");
1227 strcat(bmsg->cm_fields['M'], dsn);
1228 strcat(bmsg->cm_fields['M'], "\n");
1230 remove_token(instr, i, '\n');
1236 /* Deliver the bounce if there's anything worth mentioning */
1237 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1238 if (num_bounces > 0) {
1240 /* First try the user who sent the message */
1241 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1242 if (strlen(bounceto) == 0) {
1243 lprintf(CTDL_ERR, "No bounce address specified\n");
1244 bounce_msgid = (-1L);
1247 /* Can we deliver the bounce to the original sender? */
1248 valid = validate_recipients(bounceto);
1249 if (valid != NULL) {
1250 if (valid->num_error == 0) {
1251 CtdlSubmitMsg(bmsg, valid, "");
1252 successful_bounce = 1;
1256 /* If not, post it in the Aide> room */
1257 if (successful_bounce == 0) {
1258 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1261 /* Free up the memory we used */
1262 if (valid != NULL) {
1267 CtdlFreeMessage(bmsg);
1268 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1273 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1274 * set of delivery instructions for completed deliveries and remove them.
1276 * It returns the number of incomplete deliveries remaining.
1278 int smtp_purge_completed_deliveries(char *instr) {
1289 lines = num_tokens(instr, '\n');
1290 for (i=0; i<lines; ++i) {
1291 extract_token(buf, instr, i, '\n');
1292 extract(key, buf, 0);
1293 extract(addr, buf, 1);
1294 status = extract_int(buf, 2);
1295 extract(dsn, buf, 3);
1300 (!strcasecmp(key, "local"))
1301 || (!strcasecmp(key, "remote"))
1302 || (!strcasecmp(key, "ignet"))
1303 || (!strcasecmp(key, "room"))
1305 if (status == 2) completed = 1;
1310 remove_token(instr, i, '\n');
1323 * Called by smtp_do_queue() to handle an individual message.
1325 void smtp_do_procmsg(long msgnum, void *userdata) {
1326 struct CtdlMessage *msg;
1328 char *results = NULL;
1336 long text_msgid = (-1);
1337 int incomplete_deliveries_remaining;
1338 time_t attempted = 0L;
1339 time_t last_attempted = 0L;
1340 time_t retry = SMTP_RETRY_INTERVAL;
1342 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1344 msg = CtdlFetchMessage(msgnum);
1346 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1350 instr = strdoop(msg->cm_fields['M']);
1351 CtdlFreeMessage(msg);
1353 /* Strip out the headers amd any other non-instruction line */
1354 lines = num_tokens(instr, '\n');
1355 for (i=0; i<lines; ++i) {
1356 extract_token(buf, instr, i, '\n');
1357 if (num_tokens(buf, '|') < 2) {
1358 remove_token(instr, i, '\n');
1364 /* Learn the message ID and find out about recent delivery attempts */
1365 lines = num_tokens(instr, '\n');
1366 for (i=0; i<lines; ++i) {
1367 extract_token(buf, instr, i, '\n');
1368 extract(key, buf, 0);
1369 if (!strcasecmp(key, "msgid")) {
1370 text_msgid = extract_long(buf, 1);
1372 if (!strcasecmp(key, "retry")) {
1373 /* double the retry interval after each attempt */
1374 retry = extract_long(buf, 1) * 2L;
1375 if (retry > SMTP_RETRY_MAX) {
1376 retry = SMTP_RETRY_MAX;
1378 remove_token(instr, i, '\n');
1380 if (!strcasecmp(key, "attempted")) {
1381 attempted = extract_long(buf, 1);
1382 if (attempted > last_attempted)
1383 last_attempted = attempted;
1388 * Postpone delivery if we've already tried recently.
1390 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1391 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1398 * Bail out if there's no actual message associated with this
1400 if (text_msgid < 0L) {
1401 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1406 /* Plow through the instructions looking for 'remote' directives and
1407 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1408 * were experienced and it's time to try again)
1410 lines = num_tokens(instr, '\n');
1411 for (i=0; i<lines; ++i) {
1412 extract_token(buf, instr, i, '\n');
1413 extract(key, buf, 0);
1414 extract(addr, buf, 1);
1415 status = extract_int(buf, 2);
1416 extract(dsn, buf, 3);
1417 if ( (!strcasecmp(key, "remote"))
1418 && ((status==0)||(status==3)||(status==4)) ) {
1420 /* Remove this "remote" instruction from the set,
1421 * but replace the set's final newline if
1422 * remove_token() stripped it. It has to be there.
1424 remove_token(instr, i, '\n');
1425 if (instr[strlen(instr)-1] != '\n') {
1426 strcat(instr, "\n");
1431 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1432 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1434 if (results == NULL) {
1435 results = mallok(1024);
1436 memset(results, 0, 1024);
1439 results = reallok(results,
1440 strlen(results) + 1024);
1442 snprintf(&results[strlen(results)], 1024,
1444 key, addr, status, dsn);
1449 if (results != NULL) {
1450 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1451 strcat(instr, results);
1456 /* Generate 'bounce' messages */
1457 smtp_do_bounce(instr);
1459 /* Go through the delivery list, deleting completed deliveries */
1460 incomplete_deliveries_remaining =
1461 smtp_purge_completed_deliveries(instr);
1465 * No delivery instructions remain, so delete both the instructions
1466 * message and the message message.
1468 if (incomplete_deliveries_remaining <= 0) {
1469 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1470 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1475 * Uncompleted delivery instructions remain, so delete the old
1476 * instructions and replace with the updated ones.
1478 if (incomplete_deliveries_remaining > 0) {
1479 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1480 msg = mallok(sizeof(struct CtdlMessage));
1481 memset(msg, 0, sizeof(struct CtdlMessage));
1482 msg->cm_magic = CTDLMESSAGE_MAGIC;
1483 msg->cm_anon_type = MES_NORMAL;
1484 msg->cm_format_type = FMT_RFC822;
1485 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1486 snprintf(msg->cm_fields['M'],
1488 "Content-type: %s\n\n%s\n"
1491 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1493 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1494 CtdlFreeMessage(msg);
1504 * Run through the queue sending out messages.
1506 void smtp_do_queue(void) {
1507 static int doing_queue = 0;
1510 * This is a simple concurrency check to make sure only one queue run
1511 * is done at a time. We could do this with a mutex, but since we
1512 * don't really require extremely fine granularity here, we'll do it
1513 * with a static variable instead.
1515 if (doing_queue) return;
1519 * Go ahead and run the queue
1521 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1523 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1524 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1527 CtdlForEachMessage(MSGS_ALL, 0L,
1528 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1530 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1537 /*****************************************************************************/
1538 /* SMTP UTILITY COMMANDS */
1539 /*****************************************************************************/
1541 void cmd_smtp(char *argbuf) {
1548 if (CtdlAccessCheck(ac_aide)) return;
1550 extract(cmd, argbuf, 0);
1552 if (!strcasecmp(cmd, "mx")) {
1553 extract(node, argbuf, 1);
1554 num_mxhosts = getmx(buf, node);
1555 cprintf("%d %d MX hosts listed for %s\n",
1556 LISTING_FOLLOWS, num_mxhosts, node);
1557 for (i=0; i<num_mxhosts; ++i) {
1558 extract(node, buf, i);
1559 cprintf("%s\n", node);
1565 else if (!strcasecmp(cmd, "runqueue")) {
1567 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1572 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1579 * Initialize the SMTP outbound queue
1581 void smtp_init_spoolout(void) {
1582 struct ctdlroom qrbuf;
1585 * Create the room. This will silently fail if the room already
1586 * exists, and that's perfectly ok, because we want it to exist.
1588 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0);
1591 * Make sure it's set to be a "system room" so it doesn't show up
1592 * in the <K>nown rooms list for Aides.
1594 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1595 qrbuf.QRflags2 |= QR2_SYSTEM;
1603 /*****************************************************************************/
1604 /* MODULE INITIALIZATION STUFF */
1605 /*****************************************************************************/
1608 char *serv_smtp_init(void)
1610 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1615 CtdlRegisterServiceHook(0, /* ...and locally */
1620 smtp_init_spoolout();
1621 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1622 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");