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 2554 - SMTP Service Extension for Authentication
18 * RFC 2821 - Simple Mail Transfer Protocol
19 * RFC 2822 - Internet Message Format
20 * RFC 2920 - SMTP Service Extension for Command Pipelining
32 #include <sys/types.h>
34 #if TIME_WITH_SYS_TIME
35 # include <sys/time.h>
39 # include <sys/time.h>
49 #include <sys/socket.h>
50 #include <netinet/in.h>
51 #include <arpa/inet.h>
54 #include "sysdep_decls.h"
55 #include "citserver.h"
59 #include "serv_extensions.h"
66 #include "internet_addressing.h"
69 #include "clientsocket.h"
70 #include "locate_host.h"
77 struct citsmtp { /* Information about the current session */
80 struct ctdluser vrfy_buffer;
85 int number_of_recipients;
87 int message_originated_locally;
91 enum { /* Command states for login authentication */
97 enum { /* Delivery modes */
102 #define SMTP ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
103 #define SMTP_RECPS ((char *)CtdlGetUserData(SYM_SMTP_RECPS))
104 #define SMTP_ROOMS ((char *)CtdlGetUserData(SYM_SMTP_ROOMS))
107 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
111 /*****************************************************************************/
112 /* SMTP SERVER (INBOUND) STUFF */
113 /*****************************************************************************/
119 * Here's where our SMTP session begins its happy day.
121 void smtp_greeting(void) {
123 strcpy(CC->cs_clientname, "SMTP session");
124 CC->internal_pgm = 1;
125 CC->cs_flags |= CS_STEALTH;
126 CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
127 CtdlAllocUserData(SYM_SMTP_RECPS, SIZ);
128 CtdlAllocUserData(SYM_SMTP_ROOMS, SIZ);
129 snprintf(SMTP_RECPS, SIZ, "%s", "");
130 snprintf(SMTP_ROOMS, SIZ, "%s", "");
132 cprintf("220 %s ESMTP Citadel/UX server ready.\r\n", config.c_fqdn);
136 * LMTP is like SMTP but with some extra bonus footage added.
138 void lmtp_greeting(void) {
145 * Implement HELO and EHLO commands.
147 * which_command: 0=HELO, 1=EHLO, 2=LHLO
149 void smtp_hello(char *argbuf, int which_command) {
151 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
153 if ( (which_command != 2) && (SMTP->is_lmtp) ) {
154 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
158 if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
159 cprintf("500 LHLO is only allowed when running LMTP\r\n");
163 if (which_command == 0) {
164 cprintf("250 Hello %s (%s [%s])\r\n",
171 if (which_command == 1) {
172 cprintf("250-Hello %s (%s [%s])\r\n",
179 cprintf("250-Greetings and joyous salutations.\r\n");
181 cprintf("250-HELP\r\n");
182 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
183 cprintf("250-PIPELINING\r\n");
184 cprintf("250-AUTH=LOGIN\r\n");
185 cprintf("250 ENHANCEDSTATUSCODES\r\n");
192 * Implement HELP command.
194 void smtp_help(void) {
195 cprintf("214-Commands accepted:\r\n");
196 cprintf("214- DATA\r\n");
197 cprintf("214- EHLO\r\n");
198 cprintf("214- EXPN\r\n");
199 cprintf("214- HELO\r\n");
200 cprintf("214- HELP\r\n");
201 cprintf("214- MAIL\r\n");
202 cprintf("214- NOOP\r\n");
203 cprintf("214- QUIT\r\n");
204 cprintf("214- RCPT\r\n");
205 cprintf("214- RSET\r\n");
206 cprintf("214- VRFY\r\n");
214 void smtp_get_user(char *argbuf) {
218 CtdlDecodeBase64(username, argbuf, SIZ);
219 lprintf(9, "Trying <%s>\n", username);
220 if (CtdlLoginExistingUser(username) == login_ok) {
221 CtdlEncodeBase64(buf, "Password:", 9);
222 cprintf("334 %s\r\n", buf);
223 SMTP->command_state = smtp_password;
226 cprintf("500 5.7.0 No such user.\r\n");
227 SMTP->command_state = smtp_command;
235 void smtp_get_pass(char *argbuf) {
238 CtdlDecodeBase64(password, argbuf, SIZ);
239 lprintf(9, "Trying <%s>\n", password);
240 if (CtdlTryPassword(password) == pass_ok) {
241 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
242 lprintf(9, "SMTP authenticated login successful\n");
243 CC->internal_pgm = 0;
244 CC->cs_flags &= ~CS_STEALTH;
247 cprintf("535 5.7.0 Authentication failed.\r\n");
249 SMTP->command_state = smtp_command;
256 void smtp_auth(char *argbuf) {
259 if (strncasecmp(argbuf, "login", 5) ) {
260 cprintf("504 5.7.4 We only support LOGIN authentication.\r\n");
264 if (strlen(argbuf) >= 7) {
265 smtp_get_user(&argbuf[6]);
269 CtdlEncodeBase64(buf, "Username:", 9);
270 cprintf("334 %s\r\n", buf);
271 SMTP->command_state = smtp_user;
277 * Back end for smtp_vrfy() command
279 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
281 if (!fuzzy_match(us, SMTP->vrfy_match)) {
283 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
289 * Implements the VRFY (verify user name) command.
290 * Performs fuzzy match on full user names.
292 void smtp_vrfy(char *argbuf) {
293 SMTP->vrfy_count = 0;
294 strcpy(SMTP->vrfy_match, argbuf);
295 ForEachUser(smtp_vrfy_backend, NULL);
297 if (SMTP->vrfy_count < 1) {
298 cprintf("550 5.1.1 String does not match anything.\r\n");
300 else if (SMTP->vrfy_count == 1) {
301 cprintf("250 %s <cit%ld@%s>\r\n",
302 SMTP->vrfy_buffer.fullname,
303 SMTP->vrfy_buffer.usernum,
306 else if (SMTP->vrfy_count > 1) {
307 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
316 * Back end for smtp_expn() command
318 void smtp_expn_backend(struct ctdluser *us, void *data) {
320 if (!fuzzy_match(us, SMTP->vrfy_match)) {
322 if (SMTP->vrfy_count >= 1) {
323 cprintf("250-%s <cit%ld@%s>\r\n",
324 SMTP->vrfy_buffer.fullname,
325 SMTP->vrfy_buffer.usernum,
330 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
336 * Implements the EXPN (expand user name) command.
337 * Performs fuzzy match on full user names.
339 void smtp_expn(char *argbuf) {
340 SMTP->vrfy_count = 0;
341 strcpy(SMTP->vrfy_match, argbuf);
342 ForEachUser(smtp_expn_backend, NULL);
344 if (SMTP->vrfy_count < 1) {
345 cprintf("550 5.1.1 String does not match anything.\r\n");
347 else if (SMTP->vrfy_count >= 1) {
348 cprintf("250 %s <cit%ld@%s>\r\n",
349 SMTP->vrfy_buffer.fullname,
350 SMTP->vrfy_buffer.usernum,
357 * Implements the RSET (reset state) command.
358 * Currently this just zeroes out the state buffer. If pointers to data
359 * allocated with mallok() are ever placed in the state buffer, we have to
360 * be sure to phree() them first!
362 void smtp_rset(void) {
366 * Our entire SMTP state is discarded when a RSET command is issued,
367 * but we need to preserve this one little piece of information, so
368 * we save it for later.
370 is_lmtp = SMTP->is_lmtp;
372 memset(SMTP, 0, sizeof(struct citsmtp));
375 * It is somewhat ambiguous whether we want to log out when a RSET
376 * command is issued. Here's the code to do it. It is commented out
377 * because some clients (such as Pine) issue RSET commands before
378 * each message, but still expect to be logged in.
380 * if (CC->logged_in) {
386 * Reinstate this little piece of information we saved (see above).
388 SMTP->is_lmtp = is_lmtp;
390 cprintf("250 2.0.0 Zap!\r\n");
394 * Clear out the portions of the state buffer that need to be cleared out
395 * after the DATA command finishes.
397 void smtp_data_clear(void) {
398 strcpy(SMTP->from, "");
399 strcpy(SMTP->recipients, "");
400 SMTP->number_of_recipients = 0;
401 SMTP->delivery_mode = 0;
402 SMTP->message_originated_locally = 0;
408 * Implements the "MAIL From:" command
410 void smtp_mail(char *argbuf) {
415 if (strlen(SMTP->from) != 0) {
416 cprintf("503 5.1.0 Only one sender permitted\r\n");
420 if (strncasecmp(argbuf, "From:", 5)) {
421 cprintf("501 5.1.7 Syntax error\r\n");
425 strcpy(SMTP->from, &argbuf[5]);
427 stripallbut(SMTP->from, '<', '>');
429 /* We used to reject empty sender names, until it was brought to our
430 * attention that RFC1123 5.2.9 requires that this be allowed. So now
431 * we allow it, but replace the empty string with a fake
432 * address so we don't have to contend with the empty string causing
433 * other code to fail when it's expecting something there.
435 if (strlen(SMTP->from) == 0) {
436 strcpy(SMTP->from, "someone@somewhere.org");
439 /* If this SMTP connection is from a logged-in user, force the 'from'
440 * to be the user's Internet e-mail address as Citadel knows it.
443 strcpy(SMTP->from, CC->cs_inet_email);
444 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
445 SMTP->message_originated_locally = 1;
449 else if (SMTP->is_lmtp) {
450 /* Bypass forgery checking for LMTP */
453 /* Otherwise, make sure outsiders aren't trying to forge mail from
457 process_rfc822_addr(SMTP->from, user, node, name);
458 if (CtdlHostAlias(node) != hostalias_nomatch) {
460 "You must log in to send mail from %s\r\n",
462 strcpy(SMTP->from, "");
467 cprintf("250 2.0.0 Sender ok\r\n");
473 * Implements the "RCPT To:" command
475 void smtp_rcpt(char *argbuf) {
477 char message_to_spammer[SIZ];
478 struct recptypes *valid = NULL;
480 if (strlen(SMTP->from) == 0) {
481 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
485 if (strncasecmp(argbuf, "To:", 3)) {
486 cprintf("501 5.1.7 Syntax error\r\n");
490 strcpy(recp, &argbuf[3]);
492 stripallbut(recp, '<', '>');
494 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
495 cprintf("452 4.5.3 Too many recipients\r\n");
500 if ( (!CC->logged_in)
501 && (!SMTP->is_lmtp) ) {
502 if (rbl_check(message_to_spammer)) {
503 cprintf("550 %s\r\n", message_to_spammer);
504 /* no need to phree(valid), it's not allocated yet */
509 valid = validate_recipients(recp);
510 if (valid->num_error > 0) {
511 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
516 if (valid->num_internet > 0) {
517 if ( (SMTP->message_originated_locally == 0)
518 && (SMTP->is_lmtp == 0) ) {
519 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
525 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
526 if (strlen(SMTP->recipients) > 0) {
527 strcat(SMTP->recipients, ",");
529 strcat(SMTP->recipients, recp);
530 SMTP->number_of_recipients += 1;
537 * Implements the DATA command
539 void smtp_data(void) {
541 struct CtdlMessage *msg;
544 struct recptypes *valid;
549 if (strlen(SMTP->from) == 0) {
550 cprintf("503 5.5.1 Need MAIL command first.\r\n");
554 if (SMTP->number_of_recipients < 1) {
555 cprintf("503 5.5.1 Need RCPT command first.\r\n");
559 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
561 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
564 if (body != NULL) snprintf(body, 4096,
565 "Received: from %s (%s [%s])\n"
573 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
576 "Unable to save message: internal error.\r\n");
580 lprintf(9, "Converting message...\n");
581 msg = convert_internet_message(body);
583 /* If the user is locally authenticated, FORCE the From: header to
584 * show up as the real sender. Yes, this violates the RFC standard,
585 * but IT MAKES SENSE. If you prefer strict RFC adherence over
586 * common sense, you can disable this in the configuration.
588 * We also set the "message room name" ('O' field) to MAILROOM
589 * (which is Mail> on most systems) to prevent it from getting set
590 * to something ugly like "0000058008.Sent Items>" when the message
591 * is read with a Citadel client.
593 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
594 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
595 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
596 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
597 if (msg->cm_fields['F'] != NULL) phree(msg->cm_fields['F']);
598 if (msg->cm_fields['O'] != NULL) phree(msg->cm_fields['O']);
599 msg->cm_fields['A'] = strdoop(CC->user.fullname);
600 msg->cm_fields['N'] = strdoop(config.c_nodename);
601 msg->cm_fields['H'] = strdoop(config.c_humannode);
602 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
603 msg->cm_fields['O'] = strdoop(MAILROOM);
606 /* Submit the message into the Citadel system. */
607 valid = validate_recipients(SMTP->recipients);
609 /* If there are modules that want to scan this message before final
610 * submission (such as virus checkers or spam filters), call them now
611 * and give them an opportunity to reject the message.
613 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
615 if (scan_errors > 0) { /* We don't want this message! */
617 if (msg->cm_fields['0'] == NULL) {
618 msg->cm_fields['0'] = strdoop(
619 "5.7.1 Message rejected by filter");
622 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
625 else { /* Ok, we'll accept this message. */
626 msgnum = CtdlSubmitMsg(msg, valid, "");
628 sprintf(result, "250 2.0.0 Message accepted.\r\n");
631 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
635 /* For SMTP and ESTMP, just print the result message. For LMTP, we
636 * have to print one result message for each recipient. Since there
637 * is nothing in Citadel which would cause different recipients to
638 * have different results, we can get away with just spitting out the
639 * same message once for each recipient.
642 for (i=0; i<SMTP->number_of_recipients; ++i) {
643 cprintf("%s", result);
647 cprintf("%s", result);
650 CtdlFreeMessage(msg);
652 smtp_data_clear(); /* clear out the buffers now */
659 * Main command loop for SMTP sessions.
661 void smtp_command_loop(void) {
665 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
666 if (client_gets(cmdbuf) < 1) {
667 lprintf(3, "SMTP socket is broken. Ending session.\n");
671 lprintf(5, "SMTP: %s\n", cmdbuf);
672 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
674 lprintf(9, "CC->logged_in = %d\n", CC->logged_in);
676 if (SMTP->command_state == smtp_user) {
677 smtp_get_user(cmdbuf);
680 else if (SMTP->command_state == smtp_password) {
681 smtp_get_pass(cmdbuf);
684 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
685 smtp_auth(&cmdbuf[5]);
688 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
692 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
693 smtp_expn(&cmdbuf[5]);
696 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
697 smtp_hello(&cmdbuf[5], 0);
700 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
701 smtp_hello(&cmdbuf[5], 1);
704 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
705 smtp_hello(&cmdbuf[5], 2);
708 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
712 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
713 smtp_mail(&cmdbuf[5]);
716 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
717 cprintf("250 NOOP\r\n");
720 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
721 cprintf("221 Goodbye...\r\n");
726 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
727 smtp_rcpt(&cmdbuf[5]);
730 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
734 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
735 smtp_vrfy(&cmdbuf[5]);
739 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
747 /*****************************************************************************/
748 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
749 /*****************************************************************************/
756 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
759 void smtp_try(const char *key, const char *addr, int *status,
760 char *dsn, size_t n, long msgnum)
767 char user[SIZ], node[SIZ], name[SIZ];
773 size_t blocksize = 0;
776 /* Parse out the host portion of the recipient address */
777 process_rfc822_addr(addr, user, node, name);
779 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
782 /* Load the message out of the database into a temp file */
784 if (msg_fp == NULL) {
786 snprintf(dsn, n, "Error creating temporary file");
790 CtdlRedirectOutput(msg_fp, -1);
791 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
792 CtdlRedirectOutput(NULL, -1);
793 fseek(msg_fp, 0L, SEEK_END);
794 msg_size = ftell(msg_fp);
798 /* Extract something to send later in the 'MAIL From:' command */
799 strcpy(mailfrom, "");
803 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
804 if (!strncasecmp(buf, "From:", 5)) {
805 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
807 for (i=0; i<strlen(mailfrom); ++i) {
808 if (!isprint(mailfrom[i])) {
809 strcpy(&mailfrom[i], &mailfrom[i+1]);
814 /* Strip out parenthesized names */
817 for (i=0; i<strlen(mailfrom); ++i) {
818 if (mailfrom[i] == '(') lp = i;
819 if (mailfrom[i] == ')') rp = i;
821 if ((lp>0)&&(rp>lp)) {
822 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
825 /* Prefer brokketized names */
828 for (i=0; i<strlen(mailfrom); ++i) {
829 if (mailfrom[i] == '<') lp = i;
830 if (mailfrom[i] == '>') rp = i;
832 if ( (lp>=0) && (rp>lp) ) {
834 strcpy(mailfrom, &mailfrom[lp]);
839 } while (scan_done == 0);
840 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
842 /* Figure out what mail exchanger host we have to connect to */
843 num_mxhosts = getmx(mxhosts, node);
844 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
845 if (num_mxhosts < 1) {
847 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
852 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
853 extract(buf, mxhosts, mx);
854 lprintf(9, "Trying <%s>\n", buf);
855 sock = sock_connect(buf, "25", "tcp");
856 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
857 if (sock >= 0) lprintf(9, "Connected!\n");
858 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
862 *status = 4; /* dsn is already filled in */
866 /* Process the SMTP greeting from the server */
867 if (ml_sock_gets(sock, buf) < 0) {
869 strcpy(dsn, "Connection broken during SMTP conversation");
872 lprintf(9, "<%s\n", buf);
876 safestrncpy(dsn, &buf[4], 1023);
881 safestrncpy(dsn, &buf[4], 1023);
886 /* At this point we know we are talking to a real SMTP server */
888 /* Do a HELO command */
889 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
890 lprintf(9, ">%s", buf);
891 sock_write(sock, buf, strlen(buf));
892 if (ml_sock_gets(sock, buf) < 0) {
894 strcpy(dsn, "Connection broken during SMTP HELO");
897 lprintf(9, "<%s\n", buf);
901 safestrncpy(dsn, &buf[4], 1023);
906 safestrncpy(dsn, &buf[4], 1023);
912 /* HELO succeeded, now try the MAIL From: command */
913 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
914 lprintf(9, ">%s", buf);
915 sock_write(sock, buf, strlen(buf));
916 if (ml_sock_gets(sock, buf) < 0) {
918 strcpy(dsn, "Connection broken during SMTP MAIL");
921 lprintf(9, "<%s\n", buf);
925 safestrncpy(dsn, &buf[4], 1023);
930 safestrncpy(dsn, &buf[4], 1023);
936 /* MAIL succeeded, now try the RCPT To: command */
937 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
938 lprintf(9, ">%s", buf);
939 sock_write(sock, buf, strlen(buf));
940 if (ml_sock_gets(sock, buf) < 0) {
942 strcpy(dsn, "Connection broken during SMTP RCPT");
945 lprintf(9, "<%s\n", buf);
949 safestrncpy(dsn, &buf[4], 1023);
954 safestrncpy(dsn, &buf[4], 1023);
960 /* RCPT succeeded, now try the DATA command */
961 lprintf(9, ">DATA\n");
962 sock_write(sock, "DATA\r\n", 6);
963 if (ml_sock_gets(sock, buf) < 0) {
965 strcpy(dsn, "Connection broken during SMTP DATA");
968 lprintf(9, "<%s\n", buf);
972 safestrncpy(dsn, &buf[4], 1023);
977 safestrncpy(dsn, &buf[4], 1023);
982 /* If we reach this point, the server is expecting data */
984 while (msg_size > 0) {
985 blocksize = sizeof(buf);
986 if (blocksize > msg_size) blocksize = msg_size;
987 fread(buf, blocksize, 1, msg_fp);
988 sock_write(sock, buf, blocksize);
989 msg_size -= blocksize;
991 if (buf[blocksize-1] != 10) {
992 lprintf(5, "Possible problem: message did not correctly "
993 "terminate. (expecting 0x10, got 0x%02x)\n",
997 sock_write(sock, ".\r\n", 3);
998 if (ml_sock_gets(sock, buf) < 0) {
1000 strcpy(dsn, "Connection broken during SMTP message transmit");
1003 lprintf(9, "%s\n", buf);
1004 if (buf[0] != '2') {
1005 if (buf[0] == '4') {
1007 safestrncpy(dsn, &buf[4], 1023);
1012 safestrncpy(dsn, &buf[4], 1023);
1018 safestrncpy(dsn, &buf[4], 1023);
1021 lprintf(9, ">QUIT\n");
1022 sock_write(sock, "QUIT\r\n", 6);
1023 ml_sock_gets(sock, buf);
1024 lprintf(9, "<%s\n", buf);
1026 bail: if (msg_fp != NULL) fclose(msg_fp);
1034 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1035 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1036 * a "bounce" message (delivery status notification).
1038 void smtp_do_bounce(char *instr) {
1046 char bounceto[1024];
1047 int num_bounces = 0;
1048 int bounce_this = 0;
1049 long bounce_msgid = (-1);
1050 time_t submitted = 0L;
1051 struct CtdlMessage *bmsg = NULL;
1053 struct recptypes *valid;
1054 int successful_bounce = 0;
1056 lprintf(9, "smtp_do_bounce() called\n");
1057 strcpy(bounceto, "");
1059 lines = num_tokens(instr, '\n');
1062 /* See if it's time to give up on delivery of this message */
1063 for (i=0; i<lines; ++i) {
1064 extract_token(buf, instr, i, '\n');
1065 extract(key, buf, 0);
1066 extract(addr, buf, 1);
1067 if (!strcasecmp(key, "submitted")) {
1068 submitted = atol(addr);
1072 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1078 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1079 if (bmsg == NULL) return;
1080 memset(bmsg, 0, sizeof(struct CtdlMessage));
1082 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1083 bmsg->cm_anon_type = MES_NORMAL;
1084 bmsg->cm_format_type = 1;
1085 bmsg->cm_fields['A'] = strdoop("Citadel");
1086 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1087 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1089 if (give_up) bmsg->cm_fields['M'] = strdoop(
1090 "A message you sent could not be delivered to some or all of its recipients\n"
1091 "due to prolonged unavailability of its destination(s).\n"
1092 "Giving up on the following addresses:\n\n"
1095 else bmsg->cm_fields['M'] = strdoop(
1096 "A message you sent could not be delivered to some or all of its recipients.\n"
1097 "The following addresses were undeliverable:\n\n"
1101 * Now go through the instructions checking for stuff.
1103 for (i=0; i<lines; ++i) {
1104 extract_token(buf, instr, i, '\n');
1105 extract(key, buf, 0);
1106 extract(addr, buf, 1);
1107 status = extract_int(buf, 2);
1108 extract(dsn, buf, 3);
1111 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1112 key, addr, status, dsn);
1114 if (!strcasecmp(key, "bounceto")) {
1115 strcpy(bounceto, addr);
1119 (!strcasecmp(key, "local"))
1120 || (!strcasecmp(key, "remote"))
1121 || (!strcasecmp(key, "ignet"))
1122 || (!strcasecmp(key, "room"))
1124 if (status == 5) bounce_this = 1;
1125 if (give_up) bounce_this = 1;
1131 if (bmsg->cm_fields['M'] == NULL) {
1132 lprintf(2, "ERROR ... M field is null "
1133 "(%s:%d)\n", __FILE__, __LINE__);
1136 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1137 strlen(bmsg->cm_fields['M']) + 1024 );
1138 strcat(bmsg->cm_fields['M'], addr);
1139 strcat(bmsg->cm_fields['M'], ": ");
1140 strcat(bmsg->cm_fields['M'], dsn);
1141 strcat(bmsg->cm_fields['M'], "\n");
1143 remove_token(instr, i, '\n');
1149 /* Deliver the bounce if there's anything worth mentioning */
1150 lprintf(9, "num_bounces = %d\n", num_bounces);
1151 if (num_bounces > 0) {
1153 /* First try the user who sent the message */
1154 lprintf(9, "bounce to user? <%s>\n", bounceto);
1155 if (strlen(bounceto) == 0) {
1156 lprintf(7, "No bounce address specified\n");
1157 bounce_msgid = (-1L);
1160 /* Can we deliver the bounce to the original sender? */
1161 valid = validate_recipients(bounceto);
1162 if (valid != NULL) {
1163 if (valid->num_error == 0) {
1164 CtdlSubmitMsg(bmsg, valid, "");
1165 successful_bounce = 1;
1169 /* If not, post it in the Aide> room */
1170 if (successful_bounce == 0) {
1171 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1174 /* Free up the memory we used */
1175 if (valid != NULL) {
1180 CtdlFreeMessage(bmsg);
1181 lprintf(9, "Done processing bounces\n");
1186 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1187 * set of delivery instructions for completed deliveries and remove them.
1189 * It returns the number of incomplete deliveries remaining.
1191 int smtp_purge_completed_deliveries(char *instr) {
1202 lines = num_tokens(instr, '\n');
1203 for (i=0; i<lines; ++i) {
1204 extract_token(buf, instr, i, '\n');
1205 extract(key, buf, 0);
1206 extract(addr, buf, 1);
1207 status = extract_int(buf, 2);
1208 extract(dsn, buf, 3);
1213 (!strcasecmp(key, "local"))
1214 || (!strcasecmp(key, "remote"))
1215 || (!strcasecmp(key, "ignet"))
1216 || (!strcasecmp(key, "room"))
1218 if (status == 2) completed = 1;
1223 remove_token(instr, i, '\n');
1236 * Called by smtp_do_queue() to handle an individual message.
1238 void smtp_do_procmsg(long msgnum, void *userdata) {
1239 struct CtdlMessage *msg;
1241 char *results = NULL;
1249 long text_msgid = (-1);
1250 int incomplete_deliveries_remaining;
1251 time_t attempted = 0L;
1252 time_t last_attempted = 0L;
1253 time_t retry = SMTP_RETRY_INTERVAL;
1255 lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1257 msg = CtdlFetchMessage(msgnum);
1259 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1263 instr = strdoop(msg->cm_fields['M']);
1264 CtdlFreeMessage(msg);
1266 /* Strip out the headers amd any other non-instruction line */
1267 lines = num_tokens(instr, '\n');
1268 for (i=0; i<lines; ++i) {
1269 extract_token(buf, instr, i, '\n');
1270 if (num_tokens(buf, '|') < 2) {
1271 remove_token(instr, i, '\n');
1277 /* Learn the message ID and find out about recent delivery attempts */
1278 lines = num_tokens(instr, '\n');
1279 for (i=0; i<lines; ++i) {
1280 extract_token(buf, instr, i, '\n');
1281 extract(key, buf, 0);
1282 if (!strcasecmp(key, "msgid")) {
1283 text_msgid = extract_long(buf, 1);
1285 if (!strcasecmp(key, "retry")) {
1286 /* double the retry interval after each attempt */
1287 retry = extract_long(buf, 1) * 2L;
1288 if (retry > SMTP_RETRY_MAX) {
1289 retry = SMTP_RETRY_MAX;
1291 remove_token(instr, i, '\n');
1293 if (!strcasecmp(key, "attempted")) {
1294 attempted = extract_long(buf, 1);
1295 if (attempted > last_attempted)
1296 last_attempted = attempted;
1301 * Postpone delivery if we've already tried recently.
1303 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1304 lprintf(7, "Retry time not yet reached.\n");
1311 * Bail out if there's no actual message associated with this
1313 if (text_msgid < 0L) {
1314 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1319 /* Plow through the instructions looking for 'remote' directives and
1320 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1321 * were experienced and it's time to try again)
1323 lines = num_tokens(instr, '\n');
1324 for (i=0; i<lines; ++i) {
1325 extract_token(buf, instr, i, '\n');
1326 extract(key, buf, 0);
1327 extract(addr, buf, 1);
1328 status = extract_int(buf, 2);
1329 extract(dsn, buf, 3);
1330 if ( (!strcasecmp(key, "remote"))
1331 && ((status==0)||(status==3)||(status==4)) ) {
1333 /* Remove this "remote" instruction from the set,
1334 * but replace the set's final newline if
1335 * remove_token() stripped it. It has to be there.
1337 remove_token(instr, i, '\n');
1338 if (instr[strlen(instr)-1] != '\n') {
1339 strcat(instr, "\n");
1344 lprintf(9, "SMTP: Trying <%s>\n", addr);
1345 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1347 if (results == NULL) {
1348 results = mallok(1024);
1349 memset(results, 0, 1024);
1352 results = reallok(results,
1353 strlen(results) + 1024);
1355 snprintf(&results[strlen(results)], 1024,
1357 key, addr, status, dsn);
1362 if (results != NULL) {
1363 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1364 strcat(instr, results);
1369 /* Generate 'bounce' messages */
1370 smtp_do_bounce(instr);
1372 /* Go through the delivery list, deleting completed deliveries */
1373 incomplete_deliveries_remaining =
1374 smtp_purge_completed_deliveries(instr);
1378 * No delivery instructions remain, so delete both the instructions
1379 * message and the message message.
1381 if (incomplete_deliveries_remaining <= 0) {
1382 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1383 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1388 * Uncompleted delivery instructions remain, so delete the old
1389 * instructions and replace with the updated ones.
1391 if (incomplete_deliveries_remaining > 0) {
1392 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1393 msg = mallok(sizeof(struct CtdlMessage));
1394 memset(msg, 0, sizeof(struct CtdlMessage));
1395 msg->cm_magic = CTDLMESSAGE_MAGIC;
1396 msg->cm_anon_type = MES_NORMAL;
1397 msg->cm_format_type = FMT_RFC822;
1398 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1399 snprintf(msg->cm_fields['M'],
1401 "Content-type: %s\n\n%s\n"
1404 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1406 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1407 CtdlFreeMessage(msg);
1417 * Run through the queue sending out messages.
1419 void smtp_do_queue(void) {
1420 static int doing_queue = 0;
1423 * This is a simple concurrency check to make sure only one queue run
1424 * is done at a time. We could do this with a mutex, but since we
1425 * don't really require extremely fine granularity here, we'll do it
1426 * with a static variable instead.
1428 if (doing_queue) return;
1432 * Go ahead and run the queue
1434 lprintf(7, "SMTP: processing outbound queue\n");
1436 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1437 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1440 CtdlForEachMessage(MSGS_ALL, 0L,
1441 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1443 lprintf(7, "SMTP: queue run completed\n");
1450 /*****************************************************************************/
1451 /* SMTP UTILITY COMMANDS */
1452 /*****************************************************************************/
1454 void cmd_smtp(char *argbuf) {
1461 if (CtdlAccessCheck(ac_aide)) return;
1463 extract(cmd, argbuf, 0);
1465 if (!strcasecmp(cmd, "mx")) {
1466 extract(node, argbuf, 1);
1467 num_mxhosts = getmx(buf, node);
1468 cprintf("%d %d MX hosts listed for %s\n",
1469 LISTING_FOLLOWS, num_mxhosts, node);
1470 for (i=0; i<num_mxhosts; ++i) {
1471 extract(node, buf, i);
1472 cprintf("%s\n", node);
1478 else if (!strcasecmp(cmd, "runqueue")) {
1480 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1485 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1492 * Initialize the SMTP outbound queue
1494 void smtp_init_spoolout(void) {
1495 struct ctdlroom qrbuf;
1498 * Create the room. This will silently fail if the room already
1499 * exists, and that's perfectly ok, because we want it to exist.
1501 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0);
1504 * Make sure it's set to be a "system room" so it doesn't show up
1505 * in the <K>nown rooms list for Aides.
1507 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1508 qrbuf.QRflags2 |= QR2_SYSTEM;
1516 /*****************************************************************************/
1517 /* MODULE INITIALIZATION STUFF */
1518 /*****************************************************************************/
1521 char *serv_smtp_init(void)
1523 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1528 CtdlRegisterServiceHook(0, /* ...and locally */
1533 smtp_init_spoolout();
1534 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1535 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");