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))
95 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
99 /*****************************************************************************/
100 /* SMTP SERVER (INBOUND) STUFF */
101 /*****************************************************************************/
107 * Here's where our SMTP session begins its happy day.
109 void smtp_greeting(void) {
111 strcpy(CC->cs_clientname, "SMTP session");
112 CC->internal_pgm = 1;
113 CC->cs_flags |= CS_STEALTH;
114 CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
115 CtdlAllocUserData(SYM_SMTP_RECPS, SIZ);
116 CtdlAllocUserData(SYM_SMTP_ROOMS, SIZ);
117 snprintf(SMTP_RECPS, SIZ, "%s", "");
118 snprintf(SMTP_ROOMS, SIZ, "%s", "");
120 cprintf("220 %s ESMTP Citadel/UX server ready.\r\n", config.c_fqdn);
125 * Implement HELO and EHLO commands.
127 void smtp_hello(char *argbuf, int is_esmtp) {
129 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
132 cprintf("250 Greetings and joyous salutations.\r\n");
135 cprintf("250-Greetings and joyous salutations.\r\n");
136 cprintf("250-HELP\r\n");
137 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
138 cprintf("250-PIPELINING\r\n");
139 cprintf("250-AUTH=LOGIN\r\n");
140 cprintf("250 ENHANCEDSTATUSCODES\r\n");
146 * Implement HELP command.
148 void smtp_help(void) {
149 cprintf("214-Commands accepted:\r\n");
150 cprintf("214- DATA\r\n");
151 cprintf("214- EHLO\r\n");
152 cprintf("214- EXPN\r\n");
153 cprintf("214- HELO\r\n");
154 cprintf("214- HELP\r\n");
155 cprintf("214- MAIL\r\n");
156 cprintf("214- NOOP\r\n");
157 cprintf("214- QUIT\r\n");
158 cprintf("214- RCPT\r\n");
159 cprintf("214- RSET\r\n");
160 cprintf("214- VRFY\r\n");
168 void smtp_get_user(char *argbuf) {
172 CtdlDecodeBase64(username, argbuf, SIZ);
173 lprintf(9, "Trying <%s>\n", username);
174 if (CtdlLoginExistingUser(username) == login_ok) {
175 CtdlEncodeBase64(buf, "Password:", 9);
176 cprintf("334 %s\r\n", buf);
177 SMTP->command_state = smtp_password;
180 cprintf("500 5.7.0 No such user.\r\n");
181 SMTP->command_state = smtp_command;
189 void smtp_get_pass(char *argbuf) {
192 CtdlDecodeBase64(password, argbuf, SIZ);
193 lprintf(9, "Trying <%s>\n", password);
194 if (CtdlTryPassword(password) == pass_ok) {
195 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
196 lprintf(9, "SMTP authenticated login successful\n");
197 CC->internal_pgm = 0;
198 CC->cs_flags &= ~CS_STEALTH;
201 cprintf("500 5.7.0 Authentication failed.\r\n");
203 SMTP->command_state = smtp_command;
210 void smtp_auth(char *argbuf) {
213 if (strncasecmp(argbuf, "login", 5) ) {
214 cprintf("550 5.7.4 We only support LOGIN authentication.\r\n");
218 if (strlen(argbuf) >= 7) {
219 smtp_get_user(&argbuf[6]);
223 CtdlEncodeBase64(buf, "Username:", 9);
224 cprintf("334 %s\r\n", buf);
225 SMTP->command_state = smtp_user;
231 * Back end for smtp_vrfy() command
233 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
235 if (!fuzzy_match(us, SMTP->vrfy_match)) {
237 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
243 * Implements the VRFY (verify user name) command.
244 * Performs fuzzy match on full user names.
246 void smtp_vrfy(char *argbuf) {
247 SMTP->vrfy_count = 0;
248 strcpy(SMTP->vrfy_match, argbuf);
249 ForEachUser(smtp_vrfy_backend, NULL);
251 if (SMTP->vrfy_count < 1) {
252 cprintf("550 5.1.1 String does not match anything.\r\n");
254 else if (SMTP->vrfy_count == 1) {
255 cprintf("250 %s <cit%ld@%s>\r\n",
256 SMTP->vrfy_buffer.fullname,
257 SMTP->vrfy_buffer.usernum,
260 else if (SMTP->vrfy_count > 1) {
261 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
270 * Back end for smtp_expn() command
272 void smtp_expn_backend(struct ctdluser *us, void *data) {
274 if (!fuzzy_match(us, SMTP->vrfy_match)) {
276 if (SMTP->vrfy_count >= 1) {
277 cprintf("250-%s <cit%ld@%s>\r\n",
278 SMTP->vrfy_buffer.fullname,
279 SMTP->vrfy_buffer.usernum,
284 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
290 * Implements the EXPN (expand user name) command.
291 * Performs fuzzy match on full user names.
293 void smtp_expn(char *argbuf) {
294 SMTP->vrfy_count = 0;
295 strcpy(SMTP->vrfy_match, argbuf);
296 ForEachUser(smtp_expn_backend, NULL);
298 if (SMTP->vrfy_count < 1) {
299 cprintf("550 5.1.1 String does not match anything.\r\n");
301 else if (SMTP->vrfy_count >= 1) {
302 cprintf("250 %s <cit%ld@%s>\r\n",
303 SMTP->vrfy_buffer.fullname,
304 SMTP->vrfy_buffer.usernum,
311 * Implements the RSET (reset state) command.
312 * Currently this just zeroes out the state buffer. If pointers to data
313 * allocated with mallok() are ever placed in the state buffer, we have to
314 * be sure to phree() them first!
316 void smtp_rset(void) {
317 memset(SMTP, 0, sizeof(struct citsmtp));
320 * It is somewhat ambiguous whether we want to log out when a RSET
321 * command is issued. Here's the code to do it. It is commented out
322 * because some clients (such as Pine) issue RSET commands before
323 * each message, but still expect to be logged in.
325 * if (CC->logged_in) {
330 cprintf("250 2.0.0 Zap!\r\n");
334 * Clear out the portions of the state buffer that need to be cleared out
335 * after the DATA command finishes.
337 void smtp_data_clear(void) {
338 strcpy(SMTP->from, "");
339 strcpy(SMTP->recipients, "");
340 SMTP->number_of_recipients = 0;
341 SMTP->delivery_mode = 0;
342 SMTP->message_originated_locally = 0;
348 * Implements the "MAIL From:" command
350 void smtp_mail(char *argbuf) {
355 if (strlen(SMTP->from) != 0) {
356 cprintf("503 5.1.0 Only one sender permitted\r\n");
360 if (strncasecmp(argbuf, "From:", 5)) {
361 cprintf("501 5.1.7 Syntax error\r\n");
365 strcpy(SMTP->from, &argbuf[5]);
367 stripallbut(SMTP->from, '<', '>');
369 /* We used to reject empty sender names, until it was brought to our
370 * attention that RFC1123 5.2.9 requires that this be allowed. So now
371 * we allow it, but replace the empty string with a fake
372 * address so we don't have to contend with the empty string causing
373 * other code to fail when it's expecting something there.
375 if (strlen(SMTP->from) == 0) {
376 strcpy(SMTP->from, "someone@somewhere.org");
379 /* If this SMTP connection is from a logged-in user, force the 'from'
380 * to be the user's Internet e-mail address as Citadel knows it.
383 strcpy(SMTP->from, CC->cs_inet_email);
384 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
385 SMTP->message_originated_locally = 1;
389 /* Otherwise, make sure outsiders aren't trying to forge mail from
393 process_rfc822_addr(SMTP->from, user, node, name);
394 if (CtdlHostAlias(node) != hostalias_nomatch) {
396 "You must log in to send mail from %s\r\n",
398 strcpy(SMTP->from, "");
403 cprintf("250 2.0.0 Sender ok\r\n");
409 * Implements the "RCPT To:" command
411 void smtp_rcpt(char *argbuf) {
413 char message_to_spammer[SIZ];
414 struct recptypes *valid = NULL;
416 if (strlen(SMTP->from) == 0) {
417 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
421 if (strncasecmp(argbuf, "To:", 3)) {
422 cprintf("501 5.1.7 Syntax error\r\n");
426 strcpy(recp, &argbuf[3]);
428 stripallbut(recp, '<', '>');
430 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
431 cprintf("452 4.5.3 Too many recipients\r\n");
436 if ( (!CC->logged_in) && (!CC->is_local_socket) ) {
437 if (rbl_check(message_to_spammer)) {
438 cprintf("550 %s\r\n", message_to_spammer);
439 /* no need to phree(valid), it's not allocated yet */
444 valid = validate_recipients(recp);
445 if (valid->num_error > 0) {
446 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
451 if (valid->num_internet > 0) {
452 if (SMTP->message_originated_locally == 0) {
453 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
459 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
460 if (strlen(SMTP->recipients) > 0) {
461 strcat(SMTP->recipients, ",");
463 strcat(SMTP->recipients, recp);
464 SMTP->number_of_recipients += 1;
471 * Implements the DATA command
473 void smtp_data(void) {
475 struct CtdlMessage *msg;
478 struct recptypes *valid;
481 if (strlen(SMTP->from) == 0) {
482 cprintf("503 5.5.1 Need MAIL command first.\r\n");
486 if (SMTP->number_of_recipients < 1) {
487 cprintf("503 5.5.1 Need RCPT command first.\r\n");
491 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
493 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
496 if (body != NULL) snprintf(body, 4096,
497 "Received: from %s (%s [%s])\n"
505 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
508 "Unable to save message: internal error.\r\n");
512 lprintf(9, "Converting message...\n");
513 msg = convert_internet_message(body);
515 /* If the user is locally authenticated, FORCE the From: header to
516 * show up as the real sender. Yes, this violates the RFC standard,
517 * but IT MAKES SENSE. If you prefer strict RFC adherence over
518 * common sense, you can disable this in the configuration.
520 * We also set the "message room name" ('O' field) to MAILROOM
521 * (which is Mail> on most systems) to prevent it from getting set
522 * to something ugly like "0000058008.Sent Items>" when the message
523 * is read with a Citadel client.
525 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
526 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
527 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
528 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
529 if (msg->cm_fields['F'] != NULL) phree(msg->cm_fields['F']);
530 if (msg->cm_fields['O'] != NULL) phree(msg->cm_fields['O']);
531 msg->cm_fields['A'] = strdoop(CC->user.fullname);
532 msg->cm_fields['N'] = strdoop(config.c_nodename);
533 msg->cm_fields['H'] = strdoop(config.c_humannode);
534 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
535 msg->cm_fields['O'] = strdoop(MAILROOM);
538 /* Submit the message into the Citadel system. */
539 valid = validate_recipients(SMTP->recipients);
541 /* If there are modules that want to scan this message before final
542 * submission (such as virus checkers or spam filters), call them now
543 * and give them an opportunity to reject the message.
545 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
547 if (scan_errors > 0) { /* We don't want this message! */
549 if (msg->cm_fields['0'] == NULL) {
550 msg->cm_fields['0'] = strdoop(
551 "5.7.1 Message rejected by filter");
554 cprintf("550 %s\r\n", msg->cm_fields['0']);
557 else { /* Ok, we'll accept this message. */
558 msgnum = CtdlSubmitMsg(msg, valid, "");
560 cprintf("250 2.0.0 Message accepted.\r\n");
563 cprintf("550 5.5.0 Internal delivery error\r\n");
567 CtdlFreeMessage(msg);
569 smtp_data_clear(); /* clear out the buffers now */
576 * Main command loop for SMTP sessions.
578 void smtp_command_loop(void) {
582 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
583 if (client_gets(cmdbuf) < 1) {
584 lprintf(3, "SMTP socket is broken. Ending session.\n");
588 lprintf(5, "SMTP: %s\n", cmdbuf);
589 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
591 lprintf(9, "CC->logged_in = %d\n", CC->logged_in);
593 if (SMTP->command_state == smtp_user) {
594 smtp_get_user(cmdbuf);
597 else if (SMTP->command_state == smtp_password) {
598 smtp_get_pass(cmdbuf);
601 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
602 smtp_auth(&cmdbuf[5]);
605 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
609 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
610 smtp_hello(&cmdbuf[5], 1);
613 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
614 smtp_expn(&cmdbuf[5]);
617 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
618 smtp_hello(&cmdbuf[5], 0);
621 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
625 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
626 smtp_mail(&cmdbuf[5]);
629 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
630 cprintf("250 NOOP\r\n");
633 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
634 cprintf("221 Goodbye...\r\n");
639 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
640 smtp_rcpt(&cmdbuf[5]);
643 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
647 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
648 smtp_vrfy(&cmdbuf[5]);
652 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
660 /*****************************************************************************/
661 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
662 /*****************************************************************************/
669 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
672 void smtp_try(const char *key, const char *addr, int *status,
673 char *dsn, size_t n, long msgnum)
680 char user[SIZ], node[SIZ], name[SIZ];
686 size_t blocksize = 0;
689 /* Parse out the host portion of the recipient address */
690 process_rfc822_addr(addr, user, node, name);
692 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
695 /* Load the message out of the database into a temp file */
697 if (msg_fp == NULL) {
699 snprintf(dsn, n, "Error creating temporary file");
703 CtdlRedirectOutput(msg_fp, -1);
704 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
705 CtdlRedirectOutput(NULL, -1);
706 fseek(msg_fp, 0L, SEEK_END);
707 msg_size = ftell(msg_fp);
711 /* Extract something to send later in the 'MAIL From:' command */
712 strcpy(mailfrom, "");
716 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
717 if (!strncasecmp(buf, "From:", 5)) {
718 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
720 for (i=0; i<strlen(mailfrom); ++i) {
721 if (!isprint(mailfrom[i])) {
722 strcpy(&mailfrom[i], &mailfrom[i+1]);
727 /* Strip out parenthesized names */
730 for (i=0; i<strlen(mailfrom); ++i) {
731 if (mailfrom[i] == '(') lp = i;
732 if (mailfrom[i] == ')') rp = i;
734 if ((lp>0)&&(rp>lp)) {
735 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
738 /* Prefer brokketized names */
741 for (i=0; i<strlen(mailfrom); ++i) {
742 if (mailfrom[i] == '<') lp = i;
743 if (mailfrom[i] == '>') rp = i;
745 if ( (lp>=0) && (rp>lp) ) {
747 strcpy(mailfrom, &mailfrom[lp]);
752 } while (scan_done == 0);
753 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
755 /* Figure out what mail exchanger host we have to connect to */
756 num_mxhosts = getmx(mxhosts, node);
757 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
758 if (num_mxhosts < 1) {
760 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
765 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
766 extract(buf, mxhosts, mx);
767 lprintf(9, "Trying <%s>\n", buf);
768 sock = sock_connect(buf, "25", "tcp");
769 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
770 if (sock >= 0) lprintf(9, "Connected!\n");
771 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
775 *status = 4; /* dsn is already filled in */
779 /* Process the SMTP greeting from the server */
780 if (ml_sock_gets(sock, buf) < 0) {
782 strcpy(dsn, "Connection broken during SMTP conversation");
785 lprintf(9, "<%s\n", buf);
789 safestrncpy(dsn, &buf[4], 1023);
794 safestrncpy(dsn, &buf[4], 1023);
799 /* At this point we know we are talking to a real SMTP server */
801 /* Do a HELO command */
802 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
803 lprintf(9, ">%s", buf);
804 sock_write(sock, buf, strlen(buf));
805 if (ml_sock_gets(sock, buf) < 0) {
807 strcpy(dsn, "Connection broken during SMTP HELO");
810 lprintf(9, "<%s\n", buf);
814 safestrncpy(dsn, &buf[4], 1023);
819 safestrncpy(dsn, &buf[4], 1023);
825 /* HELO succeeded, now try the MAIL From: command */
826 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
827 lprintf(9, ">%s", buf);
828 sock_write(sock, buf, strlen(buf));
829 if (ml_sock_gets(sock, buf) < 0) {
831 strcpy(dsn, "Connection broken during SMTP MAIL");
834 lprintf(9, "<%s\n", buf);
838 safestrncpy(dsn, &buf[4], 1023);
843 safestrncpy(dsn, &buf[4], 1023);
849 /* MAIL succeeded, now try the RCPT To: command */
850 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
851 lprintf(9, ">%s", buf);
852 sock_write(sock, buf, strlen(buf));
853 if (ml_sock_gets(sock, buf) < 0) {
855 strcpy(dsn, "Connection broken during SMTP RCPT");
858 lprintf(9, "<%s\n", buf);
862 safestrncpy(dsn, &buf[4], 1023);
867 safestrncpy(dsn, &buf[4], 1023);
873 /* RCPT succeeded, now try the DATA command */
874 lprintf(9, ">DATA\n");
875 sock_write(sock, "DATA\r\n", 6);
876 if (ml_sock_gets(sock, buf) < 0) {
878 strcpy(dsn, "Connection broken during SMTP DATA");
881 lprintf(9, "<%s\n", buf);
885 safestrncpy(dsn, &buf[4], 1023);
890 safestrncpy(dsn, &buf[4], 1023);
895 /* If we reach this point, the server is expecting data */
897 while (msg_size > 0) {
898 blocksize = sizeof(buf);
899 if (blocksize > msg_size) blocksize = msg_size;
900 fread(buf, blocksize, 1, msg_fp);
901 sock_write(sock, buf, blocksize);
902 msg_size -= blocksize;
904 if (buf[blocksize-1] != 10) {
905 lprintf(5, "Possible problem: message did not correctly "
906 "terminate. (expecting 0x10, got 0x%02x)\n",
910 sock_write(sock, ".\r\n", 3);
911 if (ml_sock_gets(sock, buf) < 0) {
913 strcpy(dsn, "Connection broken during SMTP message transmit");
916 lprintf(9, "%s\n", buf);
920 safestrncpy(dsn, &buf[4], 1023);
925 safestrncpy(dsn, &buf[4], 1023);
931 safestrncpy(dsn, &buf[4], 1023);
934 lprintf(9, ">QUIT\n");
935 sock_write(sock, "QUIT\r\n", 6);
936 ml_sock_gets(sock, buf);
937 lprintf(9, "<%s\n", buf);
939 bail: if (msg_fp != NULL) fclose(msg_fp);
947 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
948 * instructions for "5" codes (permanent fatal errors) and produce/deliver
949 * a "bounce" message (delivery status notification).
951 void smtp_do_bounce(char *instr) {
962 long bounce_msgid = (-1);
963 time_t submitted = 0L;
964 struct CtdlMessage *bmsg = NULL;
966 struct recptypes *valid;
967 int successful_bounce = 0;
969 lprintf(9, "smtp_do_bounce() called\n");
970 strcpy(bounceto, "");
972 lines = num_tokens(instr, '\n');
975 /* See if it's time to give up on delivery of this message */
976 for (i=0; i<lines; ++i) {
977 extract_token(buf, instr, i, '\n');
978 extract(key, buf, 0);
979 extract(addr, buf, 1);
980 if (!strcasecmp(key, "submitted")) {
981 submitted = atol(addr);
985 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
991 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
992 if (bmsg == NULL) return;
993 memset(bmsg, 0, sizeof(struct CtdlMessage));
995 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
996 bmsg->cm_anon_type = MES_NORMAL;
997 bmsg->cm_format_type = 1;
998 bmsg->cm_fields['A'] = strdoop("Citadel");
999 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1000 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1002 if (give_up) bmsg->cm_fields['M'] = strdoop(
1003 "A message you sent could not be delivered to some or all of its recipients\n"
1004 "due to prolonged unavailability of its destination(s).\n"
1005 "Giving up on the following addresses:\n\n"
1008 else bmsg->cm_fields['M'] = strdoop(
1009 "A message you sent could not be delivered to some or all of its recipients.\n"
1010 "The following addresses were undeliverable:\n\n"
1014 * Now go through the instructions checking for stuff.
1016 for (i=0; i<lines; ++i) {
1017 extract_token(buf, instr, i, '\n');
1018 extract(key, buf, 0);
1019 extract(addr, buf, 1);
1020 status = extract_int(buf, 2);
1021 extract(dsn, buf, 3);
1024 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1025 key, addr, status, dsn);
1027 if (!strcasecmp(key, "bounceto")) {
1028 strcpy(bounceto, addr);
1032 (!strcasecmp(key, "local"))
1033 || (!strcasecmp(key, "remote"))
1034 || (!strcasecmp(key, "ignet"))
1035 || (!strcasecmp(key, "room"))
1037 if (status == 5) bounce_this = 1;
1038 if (give_up) bounce_this = 1;
1044 if (bmsg->cm_fields['M'] == NULL) {
1045 lprintf(2, "ERROR ... M field is null "
1046 "(%s:%d)\n", __FILE__, __LINE__);
1049 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1050 strlen(bmsg->cm_fields['M']) + 1024 );
1051 strcat(bmsg->cm_fields['M'], addr);
1052 strcat(bmsg->cm_fields['M'], ": ");
1053 strcat(bmsg->cm_fields['M'], dsn);
1054 strcat(bmsg->cm_fields['M'], "\n");
1056 remove_token(instr, i, '\n');
1062 /* Deliver the bounce if there's anything worth mentioning */
1063 lprintf(9, "num_bounces = %d\n", num_bounces);
1064 if (num_bounces > 0) {
1066 /* First try the user who sent the message */
1067 lprintf(9, "bounce to user? <%s>\n", bounceto);
1068 if (strlen(bounceto) == 0) {
1069 lprintf(7, "No bounce address specified\n");
1070 bounce_msgid = (-1L);
1073 /* Can we deliver the bounce to the original sender? */
1074 valid = validate_recipients(bounceto);
1075 if (valid != NULL) {
1076 if (valid->num_error == 0) {
1077 CtdlSubmitMsg(bmsg, valid, "");
1078 successful_bounce = 1;
1082 /* If not, post it in the Aide> room */
1083 if (successful_bounce == 0) {
1084 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1087 /* Free up the memory we used */
1088 if (valid != NULL) {
1093 CtdlFreeMessage(bmsg);
1094 lprintf(9, "Done processing bounces\n");
1099 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1100 * set of delivery instructions for completed deliveries and remove them.
1102 * It returns the number of incomplete deliveries remaining.
1104 int smtp_purge_completed_deliveries(char *instr) {
1115 lines = num_tokens(instr, '\n');
1116 for (i=0; i<lines; ++i) {
1117 extract_token(buf, instr, i, '\n');
1118 extract(key, buf, 0);
1119 extract(addr, buf, 1);
1120 status = extract_int(buf, 2);
1121 extract(dsn, buf, 3);
1126 (!strcasecmp(key, "local"))
1127 || (!strcasecmp(key, "remote"))
1128 || (!strcasecmp(key, "ignet"))
1129 || (!strcasecmp(key, "room"))
1131 if (status == 2) completed = 1;
1136 remove_token(instr, i, '\n');
1149 * Called by smtp_do_queue() to handle an individual message.
1151 void smtp_do_procmsg(long msgnum, void *userdata) {
1152 struct CtdlMessage *msg;
1154 char *results = NULL;
1162 long text_msgid = (-1);
1163 int incomplete_deliveries_remaining;
1164 time_t attempted = 0L;
1165 time_t last_attempted = 0L;
1166 time_t retry = SMTP_RETRY_INTERVAL;
1168 lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1170 msg = CtdlFetchMessage(msgnum);
1172 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1176 instr = strdoop(msg->cm_fields['M']);
1177 CtdlFreeMessage(msg);
1179 /* Strip out the headers amd any other non-instruction line */
1180 lines = num_tokens(instr, '\n');
1181 for (i=0; i<lines; ++i) {
1182 extract_token(buf, instr, i, '\n');
1183 if (num_tokens(buf, '|') < 2) {
1184 remove_token(instr, i, '\n');
1190 /* Learn the message ID and find out about recent delivery attempts */
1191 lines = num_tokens(instr, '\n');
1192 for (i=0; i<lines; ++i) {
1193 extract_token(buf, instr, i, '\n');
1194 extract(key, buf, 0);
1195 if (!strcasecmp(key, "msgid")) {
1196 text_msgid = extract_long(buf, 1);
1198 if (!strcasecmp(key, "retry")) {
1199 /* double the retry interval after each attempt */
1200 retry = extract_long(buf, 1) * 2L;
1201 if (retry > SMTP_RETRY_MAX) {
1202 retry = SMTP_RETRY_MAX;
1204 remove_token(instr, i, '\n');
1206 if (!strcasecmp(key, "attempted")) {
1207 attempted = extract_long(buf, 1);
1208 if (attempted > last_attempted)
1209 last_attempted = attempted;
1214 * Postpone delivery if we've already tried recently.
1216 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1217 lprintf(7, "Retry time not yet reached.\n");
1224 * Bail out if there's no actual message associated with this
1226 if (text_msgid < 0L) {
1227 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1232 /* Plow through the instructions looking for 'remote' directives and
1233 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1234 * were experienced and it's time to try again)
1236 lines = num_tokens(instr, '\n');
1237 for (i=0; i<lines; ++i) {
1238 extract_token(buf, instr, i, '\n');
1239 extract(key, buf, 0);
1240 extract(addr, buf, 1);
1241 status = extract_int(buf, 2);
1242 extract(dsn, buf, 3);
1243 if ( (!strcasecmp(key, "remote"))
1244 && ((status==0)||(status==3)||(status==4)) ) {
1246 /* Remove this "remote" instruction from the set,
1247 * but replace the set's final newline if
1248 * remove_token() stripped it. It has to be there.
1250 remove_token(instr, i, '\n');
1251 if (instr[strlen(instr)-1] != '\n') {
1252 strcat(instr, "\n");
1257 lprintf(9, "SMTP: Trying <%s>\n", addr);
1258 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1260 if (results == NULL) {
1261 results = mallok(1024);
1262 memset(results, 0, 1024);
1265 results = reallok(results,
1266 strlen(results) + 1024);
1268 snprintf(&results[strlen(results)], 1024,
1270 key, addr, status, dsn);
1275 if (results != NULL) {
1276 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1277 strcat(instr, results);
1282 /* Generate 'bounce' messages */
1283 smtp_do_bounce(instr);
1285 /* Go through the delivery list, deleting completed deliveries */
1286 incomplete_deliveries_remaining =
1287 smtp_purge_completed_deliveries(instr);
1291 * No delivery instructions remain, so delete both the instructions
1292 * message and the message message.
1294 if (incomplete_deliveries_remaining <= 0) {
1295 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1296 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1301 * Uncompleted delivery instructions remain, so delete the old
1302 * instructions and replace with the updated ones.
1304 if (incomplete_deliveries_remaining > 0) {
1305 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1306 msg = mallok(sizeof(struct CtdlMessage));
1307 memset(msg, 0, sizeof(struct CtdlMessage));
1308 msg->cm_magic = CTDLMESSAGE_MAGIC;
1309 msg->cm_anon_type = MES_NORMAL;
1310 msg->cm_format_type = FMT_RFC822;
1311 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1312 snprintf(msg->cm_fields['M'],
1314 "Content-type: %s\n\n%s\n"
1317 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1319 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1320 CtdlFreeMessage(msg);
1330 * Run through the queue sending out messages.
1332 void smtp_do_queue(void) {
1333 static int doing_queue = 0;
1336 * This is a simple concurrency check to make sure only one queue run
1337 * is done at a time. We could do this with a mutex, but since we
1338 * don't really require extremely fine granularity here, we'll do it
1339 * with a static variable instead.
1341 if (doing_queue) return;
1345 * Go ahead and run the queue
1347 lprintf(7, "SMTP: processing outbound queue\n");
1349 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1350 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1353 CtdlForEachMessage(MSGS_ALL, 0L,
1354 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1356 lprintf(7, "SMTP: queue run completed\n");
1363 /*****************************************************************************/
1364 /* SMTP UTILITY COMMANDS */
1365 /*****************************************************************************/
1367 void cmd_smtp(char *argbuf) {
1374 if (CtdlAccessCheck(ac_aide)) return;
1376 extract(cmd, argbuf, 0);
1378 if (!strcasecmp(cmd, "mx")) {
1379 extract(node, argbuf, 1);
1380 num_mxhosts = getmx(buf, node);
1381 cprintf("%d %d MX hosts listed for %s\n",
1382 LISTING_FOLLOWS, num_mxhosts, node);
1383 for (i=0; i<num_mxhosts; ++i) {
1384 extract(node, buf, i);
1385 cprintf("%s\n", node);
1391 else if (!strcasecmp(cmd, "runqueue")) {
1393 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1398 cprintf("%d Invalid command.\n", ERROR+ILLEGAL_VALUE);
1405 * Initialize the SMTP outbound queue
1407 void smtp_init_spoolout(void) {
1408 struct ctdlroom qrbuf;
1411 * Create the room. This will silently fail if the room already
1412 * exists, and that's perfectly ok, because we want it to exist.
1414 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0);
1417 * Make sure it's set to be a "system room" so it doesn't show up
1418 * in the <K>nown rooms list for Aides.
1420 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1421 qrbuf.QRflags2 |= QR2_SYSTEM;
1429 /*****************************************************************************/
1430 /* MODULE INITIALIZATION STUFF */
1431 /*****************************************************************************/
1434 char *serv_smtp_init(void)
1436 SYM_SMTP = CtdlGetDynamicSymbol();
1438 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1443 CtdlRegisterServiceHook(0, /* ...and locally */
1448 smtp_init_spoolout();
1449 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1450 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");