4 * An implementation of RFC821 (Simple Mail Transfer Protocol) for the
17 #include <sys/types.h>
19 #if TIME_WITH_SYS_TIME
20 # include <sys/time.h>
24 # include <sys/time.h>
34 #include <sys/socket.h>
35 #include <netinet/in.h>
36 #include <arpa/inet.h>
39 #include "sysdep_decls.h"
40 #include "citserver.h"
44 #include "serv_extensions.h"
51 #include "internet_addressing.h"
54 #include "clientsocket.h"
55 #include "locate_host.h"
62 struct citsmtp { /* Information about the current session */
65 struct ctdluser vrfy_buffer;
70 int number_of_recipients;
73 int message_originated_locally;
76 enum { /* Command states for login authentication */
82 enum { /* Delivery modes */
87 #define SMTP ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
88 #define SMTP_RECPS ((char *)CtdlGetUserData(SYM_SMTP_RECPS))
89 #define SMTP_ROOMS ((char *)CtdlGetUserData(SYM_SMTP_ROOMS))
92 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
96 /*****************************************************************************/
97 /* SMTP SERVER (INBOUND) STUFF */
98 /*****************************************************************************/
104 * Here's where our SMTP session begins its happy day.
106 void smtp_greeting(void) {
108 strcpy(CC->cs_clientname, "SMTP session");
109 CC->internal_pgm = 1;
110 CC->cs_flags |= CS_STEALTH;
111 CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
112 CtdlAllocUserData(SYM_SMTP_RECPS, SIZ);
113 CtdlAllocUserData(SYM_SMTP_ROOMS, SIZ);
114 snprintf(SMTP_RECPS, SIZ, "%s", "");
115 snprintf(SMTP_ROOMS, SIZ, "%s", "");
117 cprintf("220 %s ESMTP Citadel/UX server ready.\r\n", config.c_fqdn);
122 * Implement HELO and EHLO commands.
124 void smtp_hello(char *argbuf, int is_esmtp) {
126 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
129 cprintf("250 Greetings and joyous salutations.\r\n");
132 cprintf("250-Greetings and joyous salutations.\r\n");
133 cprintf("250-HELP\r\n");
134 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
135 cprintf("250-PIPELINING\r\n");
136 cprintf("250-AUTH=LOGIN\r\n");
137 cprintf("250 ENHANCEDSTATUSCODES\r\n");
143 * Implement HELP command.
145 void smtp_help(void) {
146 cprintf("214-Commands accepted:\r\n");
147 cprintf("214- DATA\r\n");
148 cprintf("214- EHLO\r\n");
149 cprintf("214- EXPN\r\n");
150 cprintf("214- HELO\r\n");
151 cprintf("214- HELP\r\n");
152 cprintf("214- MAIL\r\n");
153 cprintf("214- NOOP\r\n");
154 cprintf("214- QUIT\r\n");
155 cprintf("214- RCPT\r\n");
156 cprintf("214- RSET\r\n");
157 cprintf("214- VRFY\r\n");
165 void smtp_get_user(char *argbuf) {
169 CtdlDecodeBase64(username, argbuf, SIZ);
170 lprintf(9, "Trying <%s>\n", username);
171 if (CtdlLoginExistingUser(username) == login_ok) {
172 CtdlEncodeBase64(buf, "Password:", 9);
173 cprintf("334 %s\r\n", buf);
174 SMTP->command_state = smtp_password;
177 cprintf("500 5.7.0 No such user.\r\n");
178 SMTP->command_state = smtp_command;
186 void smtp_get_pass(char *argbuf) {
189 CtdlDecodeBase64(password, argbuf, SIZ);
190 lprintf(9, "Trying <%s>\n", password);
191 if (CtdlTryPassword(password) == pass_ok) {
192 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
193 lprintf(9, "SMTP authenticated login successful\n");
194 CC->internal_pgm = 0;
195 CC->cs_flags &= ~CS_STEALTH;
198 cprintf("500 5.7.0 Authentication failed.\r\n");
200 SMTP->command_state = smtp_command;
207 void smtp_auth(char *argbuf) {
210 if (strncasecmp(argbuf, "login", 5) ) {
211 cprintf("550 5.7.4 We only support LOGIN authentication.\r\n");
215 if (strlen(argbuf) >= 7) {
216 smtp_get_user(&argbuf[6]);
220 CtdlEncodeBase64(buf, "Username:", 9);
221 cprintf("334 %s\r\n", buf);
222 SMTP->command_state = smtp_user;
228 * Back end for smtp_vrfy() command
230 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
232 if (!fuzzy_match(us, SMTP->vrfy_match)) {
234 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
240 * Implements the VRFY (verify user name) command.
241 * Performs fuzzy match on full user names.
243 void smtp_vrfy(char *argbuf) {
244 SMTP->vrfy_count = 0;
245 strcpy(SMTP->vrfy_match, argbuf);
246 ForEachUser(smtp_vrfy_backend, NULL);
248 if (SMTP->vrfy_count < 1) {
249 cprintf("550 5.1.1 String does not match anything.\r\n");
251 else if (SMTP->vrfy_count == 1) {
252 cprintf("250 %s <cit%ld@%s>\r\n",
253 SMTP->vrfy_buffer.fullname,
254 SMTP->vrfy_buffer.usernum,
257 else if (SMTP->vrfy_count > 1) {
258 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
267 * Back end for smtp_expn() command
269 void smtp_expn_backend(struct ctdluser *us, void *data) {
271 if (!fuzzy_match(us, SMTP->vrfy_match)) {
273 if (SMTP->vrfy_count >= 1) {
274 cprintf("250-%s <cit%ld@%s>\r\n",
275 SMTP->vrfy_buffer.fullname,
276 SMTP->vrfy_buffer.usernum,
281 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
287 * Implements the EXPN (expand user name) command.
288 * Performs fuzzy match on full user names.
290 void smtp_expn(char *argbuf) {
291 SMTP->vrfy_count = 0;
292 strcpy(SMTP->vrfy_match, argbuf);
293 ForEachUser(smtp_expn_backend, NULL);
295 if (SMTP->vrfy_count < 1) {
296 cprintf("550 5.1.1 String does not match anything.\r\n");
298 else if (SMTP->vrfy_count >= 1) {
299 cprintf("250 %s <cit%ld@%s>\r\n",
300 SMTP->vrfy_buffer.fullname,
301 SMTP->vrfy_buffer.usernum,
308 * Implements the RSET (reset state) command.
309 * Currently this just zeroes out the state buffer. If pointers to data
310 * allocated with mallok() are ever placed in the state buffer, we have to
311 * be sure to phree() them first!
313 void smtp_rset(void) {
314 memset(SMTP, 0, sizeof(struct citsmtp));
317 * It is somewhat ambiguous whether we want to log out when a RSET
318 * command is issued. Here's the code to do it. It is commented out
319 * because some clients (such as Pine) issue RSET commands before
320 * each message, but still expect to be logged in.
322 * if (CC->logged_in) {
327 cprintf("250 2.0.0 Zap!\r\n");
331 * Clear out the portions of the state buffer that need to be cleared out
332 * after the DATA command finishes.
334 void smtp_data_clear(void) {
335 strcpy(SMTP->from, "");
336 strcpy(SMTP->recipients, "");
337 SMTP->number_of_recipients = 0;
338 SMTP->delivery_mode = 0;
339 SMTP->message_originated_locally = 0;
345 * Implements the "MAIL From:" command
347 void smtp_mail(char *argbuf) {
352 if (strlen(SMTP->from) != 0) {
353 cprintf("503 5.1.0 Only one sender permitted\r\n");
357 if (strncasecmp(argbuf, "From:", 5)) {
358 cprintf("501 5.1.7 Syntax error\r\n");
362 strcpy(SMTP->from, &argbuf[5]);
364 stripallbut(SMTP->from, '<', '>');
366 /* We used to reject empty sender names, until it was brought to our
367 * attention that RFC1123 5.2.9 requires that this be allowed. So now
368 * we allow it, but replace the empty string with a fake
369 * address so we don't have to contend with the empty string causing
370 * other code to fail when it's expecting something there.
372 if (strlen(SMTP->from) == 0) {
373 strcpy(SMTP->from, "someone@somewhere.org");
376 /* If this SMTP connection is from a logged-in user, force the 'from'
377 * to be the user's Internet e-mail address as Citadel knows it.
380 strcpy(SMTP->from, CC->cs_inet_email);
381 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
382 SMTP->message_originated_locally = 1;
386 /* Otherwise, make sure outsiders aren't trying to forge mail from
390 process_rfc822_addr(SMTP->from, user, node, name);
391 if (CtdlHostAlias(node) != hostalias_nomatch) {
393 "You must log in to send mail from %s\r\n",
395 strcpy(SMTP->from, "");
400 cprintf("250 2.0.0 Sender ok\r\n");
406 * Implements the "RCPT To:" command
408 void smtp_rcpt(char *argbuf) {
410 char message_to_spammer[SIZ];
411 struct recptypes *valid = NULL;
413 if (strlen(SMTP->from) == 0) {
414 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
418 if (strncasecmp(argbuf, "To:", 3)) {
419 cprintf("501 5.1.7 Syntax error\r\n");
423 strcpy(recp, &argbuf[3]);
425 stripallbut(recp, '<', '>');
427 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
428 cprintf("452 4.5.3 Too many recipients\r\n");
433 if ( (!CC->logged_in) && (!CC->is_local_socket) ) {
434 if (rbl_check(message_to_spammer)) {
435 cprintf("550 %s\r\n", message_to_spammer);
436 /* no need to phree(valid), it's not allocated yet */
441 valid = validate_recipients(recp);
442 if (valid->num_error > 0) {
443 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
448 if (valid->num_internet > 0) {
449 if (SMTP->message_originated_locally == 0) {
450 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
456 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
457 if (strlen(SMTP->recipients) > 0) {
458 strcat(SMTP->recipients, ",");
460 strcat(SMTP->recipients, recp);
461 SMTP->number_of_recipients += 1;
468 * Implements the DATA command
470 void smtp_data(void) {
472 struct CtdlMessage *msg;
475 struct recptypes *valid;
478 if (strlen(SMTP->from) == 0) {
479 cprintf("503 5.5.1 Need MAIL command first.\r\n");
483 if (SMTP->number_of_recipients < 1) {
484 cprintf("503 5.5.1 Need RCPT command first.\r\n");
488 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
490 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
493 if (body != NULL) snprintf(body, 4096,
494 "Received: from %s (%s [%s])\n"
502 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
505 "Unable to save message: internal error.\r\n");
509 lprintf(9, "Converting message...\n");
510 msg = convert_internet_message(body);
512 /* If the user is locally authenticated, FORCE the From: header to
513 * show up as the real sender. Yes, this violates the RFC standard,
514 * but IT MAKES SENSE. If you prefer strict RFC adherence over
515 * common sense, you can disable this in the configuration.
517 * We also set the "message room name" ('O' field) to MAILROOM
518 * (which is Mail> on most systems) to prevent it from getting set
519 * to something ugly like "0000058008.Sent Items>" when the message
520 * is read with a Citadel client.
522 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
523 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
524 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
525 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
526 if (msg->cm_fields['F'] != NULL) phree(msg->cm_fields['F']);
527 if (msg->cm_fields['O'] != NULL) phree(msg->cm_fields['O']);
528 msg->cm_fields['A'] = strdoop(CC->user.fullname);
529 msg->cm_fields['N'] = strdoop(config.c_nodename);
530 msg->cm_fields['H'] = strdoop(config.c_humannode);
531 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
532 msg->cm_fields['O'] = strdoop(MAILROOM);
535 /* Submit the message into the Citadel system. */
536 valid = validate_recipients(SMTP->recipients);
538 /* If there are modules that want to scan this message before final
539 * submission (such as virus checkers or spam filters), call them now
540 * and give them an opportunity to reject the message.
542 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
544 if (scan_errors > 0) { /* We don't want this message! */
546 if (msg->cm_fields['0'] == NULL) {
547 msg->cm_fields['0'] = strdoop(
548 "5.7.1 Message rejected by filter");
551 cprintf("550 %s\r\n", msg->cm_fields['0']);
554 else { /* Ok, we'll accept this message. */
555 msgnum = CtdlSubmitMsg(msg, valid, "");
557 cprintf("250 2.0.0 Message accepted.\r\n");
560 cprintf("550 5.5.0 Internal delivery error\r\n");
564 CtdlFreeMessage(msg);
566 smtp_data_clear(); /* clear out the buffers now */
573 * Main command loop for SMTP sessions.
575 void smtp_command_loop(void) {
579 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
580 if (client_gets(cmdbuf) < 1) {
581 lprintf(3, "SMTP socket is broken. Ending session.\n");
585 lprintf(5, "SMTP: %s\n", cmdbuf);
586 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
588 lprintf(9, "CC->logged_in = %d\n", CC->logged_in);
590 if (SMTP->command_state == smtp_user) {
591 smtp_get_user(cmdbuf);
594 else if (SMTP->command_state == smtp_password) {
595 smtp_get_pass(cmdbuf);
598 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
599 smtp_auth(&cmdbuf[5]);
602 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
606 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
607 smtp_hello(&cmdbuf[5], 1);
610 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
611 smtp_expn(&cmdbuf[5]);
614 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
615 smtp_hello(&cmdbuf[5], 0);
618 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
622 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
623 smtp_mail(&cmdbuf[5]);
626 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
627 cprintf("250 NOOP\r\n");
630 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
631 cprintf("221 Goodbye...\r\n");
636 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
637 smtp_rcpt(&cmdbuf[5]);
640 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
644 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
645 smtp_vrfy(&cmdbuf[5]);
649 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
657 /*****************************************************************************/
658 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
659 /*****************************************************************************/
666 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
669 void smtp_try(const char *key, const char *addr, int *status,
670 char *dsn, size_t n, long msgnum)
677 char user[SIZ], node[SIZ], name[SIZ];
683 size_t blocksize = 0;
686 /* Parse out the host portion of the recipient address */
687 process_rfc822_addr(addr, user, node, name);
689 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
692 /* Load the message out of the database into a temp file */
694 if (msg_fp == NULL) {
696 snprintf(dsn, n, "Error creating temporary file");
700 CtdlRedirectOutput(msg_fp, -1);
701 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
702 CtdlRedirectOutput(NULL, -1);
703 fseek(msg_fp, 0L, SEEK_END);
704 msg_size = ftell(msg_fp);
708 /* Extract something to send later in the 'MAIL From:' command */
709 strcpy(mailfrom, "");
713 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
714 if (!strncasecmp(buf, "From:", 5)) {
715 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
717 for (i=0; i<strlen(mailfrom); ++i) {
718 if (!isprint(mailfrom[i])) {
719 strcpy(&mailfrom[i], &mailfrom[i+1]);
724 /* Strip out parenthesized names */
727 for (i=0; i<strlen(mailfrom); ++i) {
728 if (mailfrom[i] == '(') lp = i;
729 if (mailfrom[i] == ')') rp = i;
731 if ((lp>0)&&(rp>lp)) {
732 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
735 /* Prefer brokketized names */
738 for (i=0; i<strlen(mailfrom); ++i) {
739 if (mailfrom[i] == '<') lp = i;
740 if (mailfrom[i] == '>') rp = i;
742 if ( (lp>=0) && (rp>lp) ) {
744 strcpy(mailfrom, &mailfrom[lp]);
749 } while (scan_done == 0);
750 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
752 /* Figure out what mail exchanger host we have to connect to */
753 num_mxhosts = getmx(mxhosts, node);
754 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
755 if (num_mxhosts < 1) {
757 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
762 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
763 extract(buf, mxhosts, mx);
764 lprintf(9, "Trying <%s>\n", buf);
765 sock = sock_connect(buf, "25", "tcp");
766 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
767 if (sock >= 0) lprintf(9, "Connected!\n");
768 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
772 *status = 4; /* dsn is already filled in */
776 /* Process the SMTP greeting from the server */
777 if (ml_sock_gets(sock, buf) < 0) {
779 strcpy(dsn, "Connection broken during SMTP conversation");
782 lprintf(9, "<%s\n", buf);
786 safestrncpy(dsn, &buf[4], 1023);
791 safestrncpy(dsn, &buf[4], 1023);
796 /* At this point we know we are talking to a real SMTP server */
798 /* Do a HELO command */
799 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
800 lprintf(9, ">%s", buf);
801 sock_write(sock, buf, strlen(buf));
802 if (ml_sock_gets(sock, buf) < 0) {
804 strcpy(dsn, "Connection broken during SMTP HELO");
807 lprintf(9, "<%s\n", buf);
811 safestrncpy(dsn, &buf[4], 1023);
816 safestrncpy(dsn, &buf[4], 1023);
822 /* HELO succeeded, now try the MAIL From: command */
823 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
824 lprintf(9, ">%s", buf);
825 sock_write(sock, buf, strlen(buf));
826 if (ml_sock_gets(sock, buf) < 0) {
828 strcpy(dsn, "Connection broken during SMTP MAIL");
831 lprintf(9, "<%s\n", buf);
835 safestrncpy(dsn, &buf[4], 1023);
840 safestrncpy(dsn, &buf[4], 1023);
846 /* MAIL succeeded, now try the RCPT To: command */
847 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
848 lprintf(9, ">%s", buf);
849 sock_write(sock, buf, strlen(buf));
850 if (ml_sock_gets(sock, buf) < 0) {
852 strcpy(dsn, "Connection broken during SMTP RCPT");
855 lprintf(9, "<%s\n", buf);
859 safestrncpy(dsn, &buf[4], 1023);
864 safestrncpy(dsn, &buf[4], 1023);
870 /* RCPT succeeded, now try the DATA command */
871 lprintf(9, ">DATA\n");
872 sock_write(sock, "DATA\r\n", 6);
873 if (ml_sock_gets(sock, buf) < 0) {
875 strcpy(dsn, "Connection broken during SMTP DATA");
878 lprintf(9, "<%s\n", buf);
882 safestrncpy(dsn, &buf[4], 1023);
887 safestrncpy(dsn, &buf[4], 1023);
892 /* If we reach this point, the server is expecting data */
894 while (msg_size > 0) {
895 blocksize = sizeof(buf);
896 if (blocksize > msg_size) blocksize = msg_size;
897 fread(buf, blocksize, 1, msg_fp);
898 sock_write(sock, buf, blocksize);
899 msg_size -= blocksize;
901 if (buf[blocksize-1] != 10) {
902 lprintf(5, "Possible problem: message did not correctly "
903 "terminate. (expecting 0x10, got 0x%02x)\n",
907 sock_write(sock, ".\r\n", 3);
908 if (ml_sock_gets(sock, buf) < 0) {
910 strcpy(dsn, "Connection broken during SMTP message transmit");
913 lprintf(9, "%s\n", buf);
917 safestrncpy(dsn, &buf[4], 1023);
922 safestrncpy(dsn, &buf[4], 1023);
928 safestrncpy(dsn, &buf[4], 1023);
931 lprintf(9, ">QUIT\n");
932 sock_write(sock, "QUIT\r\n", 6);
933 ml_sock_gets(sock, buf);
934 lprintf(9, "<%s\n", buf);
936 bail: if (msg_fp != NULL) fclose(msg_fp);
944 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
945 * instructions for "5" codes (permanent fatal errors) and produce/deliver
946 * a "bounce" message (delivery status notification).
948 void smtp_do_bounce(char *instr) {
959 long bounce_msgid = (-1);
960 time_t submitted = 0L;
961 struct CtdlMessage *bmsg = NULL;
963 struct recptypes *valid;
964 int successful_bounce = 0;
966 lprintf(9, "smtp_do_bounce() called\n");
967 strcpy(bounceto, "");
969 lines = num_tokens(instr, '\n');
972 /* See if it's time to give up on delivery of this message */
973 for (i=0; i<lines; ++i) {
974 extract_token(buf, instr, i, '\n');
975 extract(key, buf, 0);
976 extract(addr, buf, 1);
977 if (!strcasecmp(key, "submitted")) {
978 submitted = atol(addr);
982 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
988 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
989 if (bmsg == NULL) return;
990 memset(bmsg, 0, sizeof(struct CtdlMessage));
992 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
993 bmsg->cm_anon_type = MES_NORMAL;
994 bmsg->cm_format_type = 1;
995 bmsg->cm_fields['A'] = strdoop("Citadel");
996 bmsg->cm_fields['O'] = strdoop(MAILROOM);
997 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
999 if (give_up) bmsg->cm_fields['M'] = strdoop(
1000 "A message you sent could not be delivered to some or all of its recipients\n"
1001 "due to prolonged unavailability of its destination(s).\n"
1002 "Giving up on the following addresses:\n\n"
1005 else bmsg->cm_fields['M'] = strdoop(
1006 "A message you sent could not be delivered to some or all of its recipients.\n"
1007 "The following addresses were undeliverable:\n\n"
1011 * Now go through the instructions checking for stuff.
1013 for (i=0; i<lines; ++i) {
1014 extract_token(buf, instr, i, '\n');
1015 extract(key, buf, 0);
1016 extract(addr, buf, 1);
1017 status = extract_int(buf, 2);
1018 extract(dsn, buf, 3);
1021 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1022 key, addr, status, dsn);
1024 if (!strcasecmp(key, "bounceto")) {
1025 strcpy(bounceto, addr);
1029 (!strcasecmp(key, "local"))
1030 || (!strcasecmp(key, "remote"))
1031 || (!strcasecmp(key, "ignet"))
1032 || (!strcasecmp(key, "room"))
1034 if (status == 5) bounce_this = 1;
1035 if (give_up) bounce_this = 1;
1041 if (bmsg->cm_fields['M'] == NULL) {
1042 lprintf(2, "ERROR ... M field is null "
1043 "(%s:%d)\n", __FILE__, __LINE__);
1046 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1047 strlen(bmsg->cm_fields['M']) + 1024 );
1048 strcat(bmsg->cm_fields['M'], addr);
1049 strcat(bmsg->cm_fields['M'], ": ");
1050 strcat(bmsg->cm_fields['M'], dsn);
1051 strcat(bmsg->cm_fields['M'], "\n");
1053 remove_token(instr, i, '\n');
1059 /* Deliver the bounce if there's anything worth mentioning */
1060 lprintf(9, "num_bounces = %d\n", num_bounces);
1061 if (num_bounces > 0) {
1063 /* First try the user who sent the message */
1064 lprintf(9, "bounce to user? <%s>\n", bounceto);
1065 if (strlen(bounceto) == 0) {
1066 lprintf(7, "No bounce address specified\n");
1067 bounce_msgid = (-1L);
1070 /* Can we deliver the bounce to the original sender? */
1071 valid = validate_recipients(bounceto);
1072 if (valid != NULL) {
1073 if (valid->num_error == 0) {
1074 CtdlSubmitMsg(bmsg, valid, "");
1075 successful_bounce = 1;
1079 /* If not, post it in the Aide> room */
1080 if (successful_bounce == 0) {
1081 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1084 /* Free up the memory we used */
1085 if (valid != NULL) {
1090 CtdlFreeMessage(bmsg);
1091 lprintf(9, "Done processing bounces\n");
1096 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1097 * set of delivery instructions for completed deliveries and remove them.
1099 * It returns the number of incomplete deliveries remaining.
1101 int smtp_purge_completed_deliveries(char *instr) {
1112 lines = num_tokens(instr, '\n');
1113 for (i=0; i<lines; ++i) {
1114 extract_token(buf, instr, i, '\n');
1115 extract(key, buf, 0);
1116 extract(addr, buf, 1);
1117 status = extract_int(buf, 2);
1118 extract(dsn, buf, 3);
1123 (!strcasecmp(key, "local"))
1124 || (!strcasecmp(key, "remote"))
1125 || (!strcasecmp(key, "ignet"))
1126 || (!strcasecmp(key, "room"))
1128 if (status == 2) completed = 1;
1133 remove_token(instr, i, '\n');
1146 * Called by smtp_do_queue() to handle an individual message.
1148 void smtp_do_procmsg(long msgnum, void *userdata) {
1149 struct CtdlMessage *msg;
1151 char *results = NULL;
1159 long text_msgid = (-1);
1160 int incomplete_deliveries_remaining;
1161 time_t attempted = 0L;
1162 time_t last_attempted = 0L;
1163 time_t retry = SMTP_RETRY_INTERVAL;
1165 lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1167 msg = CtdlFetchMessage(msgnum);
1169 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1173 instr = strdoop(msg->cm_fields['M']);
1174 CtdlFreeMessage(msg);
1176 /* Strip out the headers amd any other non-instruction line */
1177 lines = num_tokens(instr, '\n');
1178 for (i=0; i<lines; ++i) {
1179 extract_token(buf, instr, i, '\n');
1180 if (num_tokens(buf, '|') < 2) {
1181 remove_token(instr, i, '\n');
1187 /* Learn the message ID and find out about recent delivery attempts */
1188 lines = num_tokens(instr, '\n');
1189 for (i=0; i<lines; ++i) {
1190 extract_token(buf, instr, i, '\n');
1191 extract(key, buf, 0);
1192 if (!strcasecmp(key, "msgid")) {
1193 text_msgid = extract_long(buf, 1);
1195 if (!strcasecmp(key, "retry")) {
1196 /* double the retry interval after each attempt */
1197 retry = extract_long(buf, 1) * 2L;
1198 if (retry > SMTP_RETRY_MAX) {
1199 retry = SMTP_RETRY_MAX;
1201 remove_token(instr, i, '\n');
1203 if (!strcasecmp(key, "attempted")) {
1204 attempted = extract_long(buf, 1);
1205 if (attempted > last_attempted)
1206 last_attempted = attempted;
1211 * Postpone delivery if we've already tried recently.
1213 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1214 lprintf(7, "Retry time not yet reached.\n");
1221 * Bail out if there's no actual message associated with this
1223 if (text_msgid < 0L) {
1224 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1229 /* Plow through the instructions looking for 'remote' directives and
1230 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1231 * were experienced and it's time to try again)
1233 lines = num_tokens(instr, '\n');
1234 for (i=0; i<lines; ++i) {
1235 extract_token(buf, instr, i, '\n');
1236 extract(key, buf, 0);
1237 extract(addr, buf, 1);
1238 status = extract_int(buf, 2);
1239 extract(dsn, buf, 3);
1240 if ( (!strcasecmp(key, "remote"))
1241 && ((status==0)||(status==3)||(status==4)) ) {
1243 /* Remove this "remote" instruction from the set,
1244 * but replace the set's final newline if
1245 * remove_token() stripped it. It has to be there.
1247 remove_token(instr, i, '\n');
1248 if (instr[strlen(instr)-1] != '\n') {
1249 strcat(instr, "\n");
1254 lprintf(9, "SMTP: Trying <%s>\n", addr);
1255 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1257 if (results == NULL) {
1258 results = mallok(1024);
1259 memset(results, 0, 1024);
1262 results = reallok(results,
1263 strlen(results) + 1024);
1265 snprintf(&results[strlen(results)], 1024,
1267 key, addr, status, dsn);
1272 if (results != NULL) {
1273 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1274 strcat(instr, results);
1279 /* Generate 'bounce' messages */
1280 smtp_do_bounce(instr);
1282 /* Go through the delivery list, deleting completed deliveries */
1283 incomplete_deliveries_remaining =
1284 smtp_purge_completed_deliveries(instr);
1288 * No delivery instructions remain, so delete both the instructions
1289 * message and the message message.
1291 if (incomplete_deliveries_remaining <= 0) {
1292 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1293 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1298 * Uncompleted delivery instructions remain, so delete the old
1299 * instructions and replace with the updated ones.
1301 if (incomplete_deliveries_remaining > 0) {
1302 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1303 msg = mallok(sizeof(struct CtdlMessage));
1304 memset(msg, 0, sizeof(struct CtdlMessage));
1305 msg->cm_magic = CTDLMESSAGE_MAGIC;
1306 msg->cm_anon_type = MES_NORMAL;
1307 msg->cm_format_type = FMT_RFC822;
1308 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1309 snprintf(msg->cm_fields['M'],
1311 "Content-type: %s\n\n%s\n"
1314 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1316 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1317 CtdlFreeMessage(msg);
1327 * Run through the queue sending out messages.
1329 void smtp_do_queue(void) {
1330 static int doing_queue = 0;
1333 * This is a simple concurrency check to make sure only one queue run
1334 * is done at a time. We could do this with a mutex, but since we
1335 * don't really require extremely fine granularity here, we'll do it
1336 * with a static variable instead.
1338 if (doing_queue) return;
1342 * Go ahead and run the queue
1344 lprintf(7, "SMTP: processing outbound queue\n");
1346 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1347 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1350 CtdlForEachMessage(MSGS_ALL, 0L,
1351 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1353 lprintf(7, "SMTP: queue run completed\n");
1360 /*****************************************************************************/
1361 /* SMTP UTILITY COMMANDS */
1362 /*****************************************************************************/
1364 void cmd_smtp(char *argbuf) {
1371 if (CtdlAccessCheck(ac_aide)) return;
1373 extract(cmd, argbuf, 0);
1375 if (!strcasecmp(cmd, "mx")) {
1376 extract(node, argbuf, 1);
1377 num_mxhosts = getmx(buf, node);
1378 cprintf("%d %d MX hosts listed for %s\n",
1379 LISTING_FOLLOWS, num_mxhosts, node);
1380 for (i=0; i<num_mxhosts; ++i) {
1381 extract(node, buf, i);
1382 cprintf("%s\n", node);
1388 else if (!strcasecmp(cmd, "runqueue")) {
1390 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1395 cprintf("%d Invalid command.\n", ERROR+ILLEGAL_VALUE);
1402 * Initialize the SMTP outbound queue
1404 void smtp_init_spoolout(void) {
1405 struct ctdlroom qrbuf;
1408 * Create the room. This will silently fail if the room already
1409 * exists, and that's perfectly ok, because we want it to exist.
1411 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0);
1414 * Make sure it's set to be a "system room" so it doesn't show up
1415 * in the <K>nown rooms list for Aides.
1417 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1418 qrbuf.QRflags2 |= QR2_SYSTEM;
1426 /*****************************************************************************/
1427 /* MODULE INITIALIZATION STUFF */
1428 /*****************************************************************************/
1431 char *serv_smtp_init(void)
1433 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1438 CtdlRegisterServiceHook(0, /* ...and locally */
1443 smtp_init_spoolout();
1444 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1445 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");