4 * This module implements the following protocols for the Citadel system:
6 * RFC 821 (Simple Mail Transfer Protocol)
7 * RFC 1854 (command pipelining)
8 * RFC 1869 (Extended Simple Mail Transfer Protocol)
9 * RFC 2033 (Local Mail Transfer Protocol)
10 * RFC 2034 (enhanced status codes)
22 #include <sys/types.h>
24 #if TIME_WITH_SYS_TIME
25 # include <sys/time.h>
29 # include <sys/time.h>
39 #include <sys/socket.h>
40 #include <netinet/in.h>
41 #include <arpa/inet.h>
44 #include "sysdep_decls.h"
45 #include "citserver.h"
49 #include "serv_extensions.h"
56 #include "internet_addressing.h"
59 #include "clientsocket.h"
60 #include "locate_host.h"
67 struct citsmtp { /* Information about the current session */
70 struct ctdluser vrfy_buffer;
75 int number_of_recipients;
77 int message_originated_locally;
81 enum { /* Command states for login authentication */
87 enum { /* Delivery modes */
92 #define SMTP ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
93 #define SMTP_RECPS ((char *)CtdlGetUserData(SYM_SMTP_RECPS))
94 #define SMTP_ROOMS ((char *)CtdlGetUserData(SYM_SMTP_ROOMS))
97 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
101 /*****************************************************************************/
102 /* SMTP SERVER (INBOUND) STUFF */
103 /*****************************************************************************/
109 * Here's where our SMTP session begins its happy day.
111 void smtp_greeting(void) {
113 strcpy(CC->cs_clientname, "SMTP session");
114 CC->internal_pgm = 1;
115 CC->cs_flags |= CS_STEALTH;
116 CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
117 CtdlAllocUserData(SYM_SMTP_RECPS, SIZ);
118 CtdlAllocUserData(SYM_SMTP_ROOMS, SIZ);
119 snprintf(SMTP_RECPS, SIZ, "%s", "");
120 snprintf(SMTP_ROOMS, SIZ, "%s", "");
122 cprintf("220 %s ESMTP Citadel/UX server ready.\r\n", config.c_fqdn);
126 * LMTP is like SMTP but with some extra bonus footage added.
128 void lmtp_greeting(void) {
135 * Implement HELO and EHLO commands.
137 * which_command: 0=HELO, 1=EHLO, 2=LHLO
139 void smtp_hello(char *argbuf, int which_command) {
141 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
143 if ( (which_command != 2) && (SMTP->is_lmtp) ) {
144 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
148 if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
149 cprintf("500 LHLO is only allowed when running LMTP\r\n");
153 if (which_command == 0) {
154 cprintf("250 Greetings and joyous salutations.\r\n");
157 cprintf("250-Greetings and joyous salutations.\r\n");
158 cprintf("250-HELP\r\n");
159 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
160 cprintf("250-PIPELINING\r\n");
161 cprintf("250-AUTH=LOGIN\r\n");
162 cprintf("250 ENHANCEDSTATUSCODES\r\n");
168 * Implement HELP command.
170 void smtp_help(void) {
171 cprintf("214-Commands accepted:\r\n");
172 cprintf("214- DATA\r\n");
173 cprintf("214- EHLO\r\n");
174 cprintf("214- EXPN\r\n");
175 cprintf("214- HELO\r\n");
176 cprintf("214- HELP\r\n");
177 cprintf("214- MAIL\r\n");
178 cprintf("214- NOOP\r\n");
179 cprintf("214- QUIT\r\n");
180 cprintf("214- RCPT\r\n");
181 cprintf("214- RSET\r\n");
182 cprintf("214- VRFY\r\n");
190 void smtp_get_user(char *argbuf) {
194 CtdlDecodeBase64(username, argbuf, SIZ);
195 lprintf(9, "Trying <%s>\n", username);
196 if (CtdlLoginExistingUser(username) == login_ok) {
197 CtdlEncodeBase64(buf, "Password:", 9);
198 cprintf("334 %s\r\n", buf);
199 SMTP->command_state = smtp_password;
202 cprintf("500 5.7.0 No such user.\r\n");
203 SMTP->command_state = smtp_command;
211 void smtp_get_pass(char *argbuf) {
214 CtdlDecodeBase64(password, argbuf, SIZ);
215 lprintf(9, "Trying <%s>\n", password);
216 if (CtdlTryPassword(password) == pass_ok) {
217 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
218 lprintf(9, "SMTP authenticated login successful\n");
219 CC->internal_pgm = 0;
220 CC->cs_flags &= ~CS_STEALTH;
223 cprintf("500 5.7.0 Authentication failed.\r\n");
225 SMTP->command_state = smtp_command;
232 void smtp_auth(char *argbuf) {
235 if (strncasecmp(argbuf, "login", 5) ) {
236 cprintf("550 5.7.4 We only support LOGIN authentication.\r\n");
240 if (strlen(argbuf) >= 7) {
241 smtp_get_user(&argbuf[6]);
245 CtdlEncodeBase64(buf, "Username:", 9);
246 cprintf("334 %s\r\n", buf);
247 SMTP->command_state = smtp_user;
253 * Back end for smtp_vrfy() command
255 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
257 if (!fuzzy_match(us, SMTP->vrfy_match)) {
259 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
265 * Implements the VRFY (verify user name) command.
266 * Performs fuzzy match on full user names.
268 void smtp_vrfy(char *argbuf) {
269 SMTP->vrfy_count = 0;
270 strcpy(SMTP->vrfy_match, argbuf);
271 ForEachUser(smtp_vrfy_backend, NULL);
273 if (SMTP->vrfy_count < 1) {
274 cprintf("550 5.1.1 String does not match anything.\r\n");
276 else if (SMTP->vrfy_count == 1) {
277 cprintf("250 %s <cit%ld@%s>\r\n",
278 SMTP->vrfy_buffer.fullname,
279 SMTP->vrfy_buffer.usernum,
282 else if (SMTP->vrfy_count > 1) {
283 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
292 * Back end for smtp_expn() command
294 void smtp_expn_backend(struct ctdluser *us, void *data) {
296 if (!fuzzy_match(us, SMTP->vrfy_match)) {
298 if (SMTP->vrfy_count >= 1) {
299 cprintf("250-%s <cit%ld@%s>\r\n",
300 SMTP->vrfy_buffer.fullname,
301 SMTP->vrfy_buffer.usernum,
306 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
312 * Implements the EXPN (expand user name) command.
313 * Performs fuzzy match on full user names.
315 void smtp_expn(char *argbuf) {
316 SMTP->vrfy_count = 0;
317 strcpy(SMTP->vrfy_match, argbuf);
318 ForEachUser(smtp_expn_backend, NULL);
320 if (SMTP->vrfy_count < 1) {
321 cprintf("550 5.1.1 String does not match anything.\r\n");
323 else if (SMTP->vrfy_count >= 1) {
324 cprintf("250 %s <cit%ld@%s>\r\n",
325 SMTP->vrfy_buffer.fullname,
326 SMTP->vrfy_buffer.usernum,
333 * Implements the RSET (reset state) command.
334 * Currently this just zeroes out the state buffer. If pointers to data
335 * allocated with mallok() are ever placed in the state buffer, we have to
336 * be sure to phree() them first!
338 void smtp_rset(void) {
339 memset(SMTP, 0, sizeof(struct citsmtp));
342 * It is somewhat ambiguous whether we want to log out when a RSET
343 * command is issued. Here's the code to do it. It is commented out
344 * because some clients (such as Pine) issue RSET commands before
345 * each message, but still expect to be logged in.
347 * if (CC->logged_in) {
352 cprintf("250 2.0.0 Zap!\r\n");
356 * Clear out the portions of the state buffer that need to be cleared out
357 * after the DATA command finishes.
359 void smtp_data_clear(void) {
360 strcpy(SMTP->from, "");
361 strcpy(SMTP->recipients, "");
362 SMTP->number_of_recipients = 0;
363 SMTP->delivery_mode = 0;
364 SMTP->message_originated_locally = 0;
370 * Implements the "MAIL From:" command
372 void smtp_mail(char *argbuf) {
377 if (strlen(SMTP->from) != 0) {
378 cprintf("503 5.1.0 Only one sender permitted\r\n");
382 if (strncasecmp(argbuf, "From:", 5)) {
383 cprintf("501 5.1.7 Syntax error\r\n");
387 strcpy(SMTP->from, &argbuf[5]);
389 stripallbut(SMTP->from, '<', '>');
391 /* We used to reject empty sender names, until it was brought to our
392 * attention that RFC1123 5.2.9 requires that this be allowed. So now
393 * we allow it, but replace the empty string with a fake
394 * address so we don't have to contend with the empty string causing
395 * other code to fail when it's expecting something there.
397 if (strlen(SMTP->from) == 0) {
398 strcpy(SMTP->from, "someone@somewhere.org");
401 /* If this SMTP connection is from a logged-in user, force the 'from'
402 * to be the user's Internet e-mail address as Citadel knows it.
405 strcpy(SMTP->from, CC->cs_inet_email);
406 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
407 SMTP->message_originated_locally = 1;
411 else if (SMTP->is_lmtp) {
412 /* Bypass forgery checking for LMTP */
415 /* Otherwise, make sure outsiders aren't trying to forge mail from
419 process_rfc822_addr(SMTP->from, user, node, name);
420 if (CtdlHostAlias(node) != hostalias_nomatch) {
422 "You must log in to send mail from %s\r\n",
424 strcpy(SMTP->from, "");
429 cprintf("250 2.0.0 Sender ok\r\n");
435 * Implements the "RCPT To:" command
437 void smtp_rcpt(char *argbuf) {
439 char message_to_spammer[SIZ];
440 struct recptypes *valid = NULL;
442 if (strlen(SMTP->from) == 0) {
443 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
447 if (strncasecmp(argbuf, "To:", 3)) {
448 cprintf("501 5.1.7 Syntax error\r\n");
452 strcpy(recp, &argbuf[3]);
454 stripallbut(recp, '<', '>');
456 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
457 cprintf("452 4.5.3 Too many recipients\r\n");
462 if ( (!CC->logged_in)
463 && (!SMTP->is_lmtp) ) {
464 if (rbl_check(message_to_spammer)) {
465 cprintf("550 %s\r\n", message_to_spammer);
466 /* no need to phree(valid), it's not allocated yet */
471 valid = validate_recipients(recp);
472 if (valid->num_error > 0) {
473 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
478 if (valid->num_internet > 0) {
479 if ( (SMTP->message_originated_locally == 0)
480 && (SMTP->is_lmtp == 0) ) {
481 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
487 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
488 if (strlen(SMTP->recipients) > 0) {
489 strcat(SMTP->recipients, ",");
491 strcat(SMTP->recipients, recp);
492 SMTP->number_of_recipients += 1;
499 * Implements the DATA command
501 void smtp_data(void) {
503 struct CtdlMessage *msg;
506 struct recptypes *valid;
511 if (strlen(SMTP->from) == 0) {
512 cprintf("503 5.5.1 Need MAIL command first.\r\n");
516 if (SMTP->number_of_recipients < 1) {
517 cprintf("503 5.5.1 Need RCPT command first.\r\n");
521 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
523 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
526 if (body != NULL) snprintf(body, 4096,
527 "Received: from %s (%s [%s])\n"
535 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
538 "Unable to save message: internal error.\r\n");
542 lprintf(9, "Converting message...\n");
543 msg = convert_internet_message(body);
545 /* If the user is locally authenticated, FORCE the From: header to
546 * show up as the real sender. Yes, this violates the RFC standard,
547 * but IT MAKES SENSE. If you prefer strict RFC adherence over
548 * common sense, you can disable this in the configuration.
550 * We also set the "message room name" ('O' field) to MAILROOM
551 * (which is Mail> on most systems) to prevent it from getting set
552 * to something ugly like "0000058008.Sent Items>" when the message
553 * is read with a Citadel client.
555 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
556 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
557 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
558 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
559 if (msg->cm_fields['F'] != NULL) phree(msg->cm_fields['F']);
560 if (msg->cm_fields['O'] != NULL) phree(msg->cm_fields['O']);
561 msg->cm_fields['A'] = strdoop(CC->user.fullname);
562 msg->cm_fields['N'] = strdoop(config.c_nodename);
563 msg->cm_fields['H'] = strdoop(config.c_humannode);
564 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
565 msg->cm_fields['O'] = strdoop(MAILROOM);
568 /* Submit the message into the Citadel system. */
569 valid = validate_recipients(SMTP->recipients);
571 /* If there are modules that want to scan this message before final
572 * submission (such as virus checkers or spam filters), call them now
573 * and give them an opportunity to reject the message.
575 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
577 if (scan_errors > 0) { /* We don't want this message! */
579 if (msg->cm_fields['0'] == NULL) {
580 msg->cm_fields['0'] = strdoop(
581 "5.7.1 Message rejected by filter");
584 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
587 else { /* Ok, we'll accept this message. */
588 msgnum = CtdlSubmitMsg(msg, valid, "");
590 sprintf(result, "250 2.0.0 Message accepted.\r\n");
593 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
597 /* For SMTP and ESTMP, just print the result message. For LMTP, we
598 * have to print one result message for each recipient. Since there
599 * is nothing in Citadel which would cause different recipients to
600 * have different results, we can get away with just spitting out the
601 * same message once for each recipient.
604 for (i=0; i<SMTP->number_of_recipients; ++i) {
605 cprintf("%s", result);
609 cprintf("%s", result);
612 CtdlFreeMessage(msg);
614 smtp_data_clear(); /* clear out the buffers now */
621 * Main command loop for SMTP sessions.
623 void smtp_command_loop(void) {
627 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
628 if (client_gets(cmdbuf) < 1) {
629 lprintf(3, "SMTP socket is broken. Ending session.\n");
633 lprintf(5, "SMTP: %s\n", cmdbuf);
634 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
636 lprintf(9, "CC->logged_in = %d\n", CC->logged_in);
638 if (SMTP->command_state == smtp_user) {
639 smtp_get_user(cmdbuf);
642 else if (SMTP->command_state == smtp_password) {
643 smtp_get_pass(cmdbuf);
646 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
647 smtp_auth(&cmdbuf[5]);
650 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
654 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
655 smtp_expn(&cmdbuf[5]);
658 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
659 smtp_hello(&cmdbuf[5], 0);
662 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
663 smtp_hello(&cmdbuf[5], 1);
666 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
667 smtp_hello(&cmdbuf[5], 2);
670 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
674 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
675 smtp_mail(&cmdbuf[5]);
678 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
679 cprintf("250 NOOP\r\n");
682 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
683 cprintf("221 Goodbye...\r\n");
688 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
689 smtp_rcpt(&cmdbuf[5]);
692 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
696 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
697 smtp_vrfy(&cmdbuf[5]);
701 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
709 /*****************************************************************************/
710 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
711 /*****************************************************************************/
718 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
721 void smtp_try(const char *key, const char *addr, int *status,
722 char *dsn, size_t n, long msgnum)
729 char user[SIZ], node[SIZ], name[SIZ];
735 size_t blocksize = 0;
738 /* Parse out the host portion of the recipient address */
739 process_rfc822_addr(addr, user, node, name);
741 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
744 /* Load the message out of the database into a temp file */
746 if (msg_fp == NULL) {
748 snprintf(dsn, n, "Error creating temporary file");
752 CtdlRedirectOutput(msg_fp, -1);
753 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
754 CtdlRedirectOutput(NULL, -1);
755 fseek(msg_fp, 0L, SEEK_END);
756 msg_size = ftell(msg_fp);
760 /* Extract something to send later in the 'MAIL From:' command */
761 strcpy(mailfrom, "");
765 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
766 if (!strncasecmp(buf, "From:", 5)) {
767 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
769 for (i=0; i<strlen(mailfrom); ++i) {
770 if (!isprint(mailfrom[i])) {
771 strcpy(&mailfrom[i], &mailfrom[i+1]);
776 /* Strip out parenthesized names */
779 for (i=0; i<strlen(mailfrom); ++i) {
780 if (mailfrom[i] == '(') lp = i;
781 if (mailfrom[i] == ')') rp = i;
783 if ((lp>0)&&(rp>lp)) {
784 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
787 /* Prefer brokketized names */
790 for (i=0; i<strlen(mailfrom); ++i) {
791 if (mailfrom[i] == '<') lp = i;
792 if (mailfrom[i] == '>') rp = i;
794 if ( (lp>=0) && (rp>lp) ) {
796 strcpy(mailfrom, &mailfrom[lp]);
801 } while (scan_done == 0);
802 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
804 /* Figure out what mail exchanger host we have to connect to */
805 num_mxhosts = getmx(mxhosts, node);
806 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
807 if (num_mxhosts < 1) {
809 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
814 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
815 extract(buf, mxhosts, mx);
816 lprintf(9, "Trying <%s>\n", buf);
817 sock = sock_connect(buf, "25", "tcp");
818 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
819 if (sock >= 0) lprintf(9, "Connected!\n");
820 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
824 *status = 4; /* dsn is already filled in */
828 /* Process the SMTP greeting from the server */
829 if (ml_sock_gets(sock, buf) < 0) {
831 strcpy(dsn, "Connection broken during SMTP conversation");
834 lprintf(9, "<%s\n", buf);
838 safestrncpy(dsn, &buf[4], 1023);
843 safestrncpy(dsn, &buf[4], 1023);
848 /* At this point we know we are talking to a real SMTP server */
850 /* Do a HELO command */
851 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
852 lprintf(9, ">%s", buf);
853 sock_write(sock, buf, strlen(buf));
854 if (ml_sock_gets(sock, buf) < 0) {
856 strcpy(dsn, "Connection broken during SMTP HELO");
859 lprintf(9, "<%s\n", buf);
863 safestrncpy(dsn, &buf[4], 1023);
868 safestrncpy(dsn, &buf[4], 1023);
874 /* HELO succeeded, now try the MAIL From: command */
875 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
876 lprintf(9, ">%s", buf);
877 sock_write(sock, buf, strlen(buf));
878 if (ml_sock_gets(sock, buf) < 0) {
880 strcpy(dsn, "Connection broken during SMTP MAIL");
883 lprintf(9, "<%s\n", buf);
887 safestrncpy(dsn, &buf[4], 1023);
892 safestrncpy(dsn, &buf[4], 1023);
898 /* MAIL succeeded, now try the RCPT To: command */
899 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
900 lprintf(9, ">%s", buf);
901 sock_write(sock, buf, strlen(buf));
902 if (ml_sock_gets(sock, buf) < 0) {
904 strcpy(dsn, "Connection broken during SMTP RCPT");
907 lprintf(9, "<%s\n", buf);
911 safestrncpy(dsn, &buf[4], 1023);
916 safestrncpy(dsn, &buf[4], 1023);
922 /* RCPT succeeded, now try the DATA command */
923 lprintf(9, ">DATA\n");
924 sock_write(sock, "DATA\r\n", 6);
925 if (ml_sock_gets(sock, buf) < 0) {
927 strcpy(dsn, "Connection broken during SMTP DATA");
930 lprintf(9, "<%s\n", buf);
934 safestrncpy(dsn, &buf[4], 1023);
939 safestrncpy(dsn, &buf[4], 1023);
944 /* If we reach this point, the server is expecting data */
946 while (msg_size > 0) {
947 blocksize = sizeof(buf);
948 if (blocksize > msg_size) blocksize = msg_size;
949 fread(buf, blocksize, 1, msg_fp);
950 sock_write(sock, buf, blocksize);
951 msg_size -= blocksize;
953 if (buf[blocksize-1] != 10) {
954 lprintf(5, "Possible problem: message did not correctly "
955 "terminate. (expecting 0x10, got 0x%02x)\n",
959 sock_write(sock, ".\r\n", 3);
960 if (ml_sock_gets(sock, buf) < 0) {
962 strcpy(dsn, "Connection broken during SMTP message transmit");
965 lprintf(9, "%s\n", buf);
969 safestrncpy(dsn, &buf[4], 1023);
974 safestrncpy(dsn, &buf[4], 1023);
980 safestrncpy(dsn, &buf[4], 1023);
983 lprintf(9, ">QUIT\n");
984 sock_write(sock, "QUIT\r\n", 6);
985 ml_sock_gets(sock, buf);
986 lprintf(9, "<%s\n", buf);
988 bail: if (msg_fp != NULL) fclose(msg_fp);
996 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
997 * instructions for "5" codes (permanent fatal errors) and produce/deliver
998 * a "bounce" message (delivery status notification).
1000 void smtp_do_bounce(char *instr) {
1008 char bounceto[1024];
1009 int num_bounces = 0;
1010 int bounce_this = 0;
1011 long bounce_msgid = (-1);
1012 time_t submitted = 0L;
1013 struct CtdlMessage *bmsg = NULL;
1015 struct recptypes *valid;
1016 int successful_bounce = 0;
1018 lprintf(9, "smtp_do_bounce() called\n");
1019 strcpy(bounceto, "");
1021 lines = num_tokens(instr, '\n');
1024 /* See if it's time to give up on delivery of this message */
1025 for (i=0; i<lines; ++i) {
1026 extract_token(buf, instr, i, '\n');
1027 extract(key, buf, 0);
1028 extract(addr, buf, 1);
1029 if (!strcasecmp(key, "submitted")) {
1030 submitted = atol(addr);
1034 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1040 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1041 if (bmsg == NULL) return;
1042 memset(bmsg, 0, sizeof(struct CtdlMessage));
1044 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1045 bmsg->cm_anon_type = MES_NORMAL;
1046 bmsg->cm_format_type = 1;
1047 bmsg->cm_fields['A'] = strdoop("Citadel");
1048 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1049 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1051 if (give_up) bmsg->cm_fields['M'] = strdoop(
1052 "A message you sent could not be delivered to some or all of its recipients\n"
1053 "due to prolonged unavailability of its destination(s).\n"
1054 "Giving up on the following addresses:\n\n"
1057 else bmsg->cm_fields['M'] = strdoop(
1058 "A message you sent could not be delivered to some or all of its recipients.\n"
1059 "The following addresses were undeliverable:\n\n"
1063 * Now go through the instructions checking for stuff.
1065 for (i=0; i<lines; ++i) {
1066 extract_token(buf, instr, i, '\n');
1067 extract(key, buf, 0);
1068 extract(addr, buf, 1);
1069 status = extract_int(buf, 2);
1070 extract(dsn, buf, 3);
1073 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1074 key, addr, status, dsn);
1076 if (!strcasecmp(key, "bounceto")) {
1077 strcpy(bounceto, addr);
1081 (!strcasecmp(key, "local"))
1082 || (!strcasecmp(key, "remote"))
1083 || (!strcasecmp(key, "ignet"))
1084 || (!strcasecmp(key, "room"))
1086 if (status == 5) bounce_this = 1;
1087 if (give_up) bounce_this = 1;
1093 if (bmsg->cm_fields['M'] == NULL) {
1094 lprintf(2, "ERROR ... M field is null "
1095 "(%s:%d)\n", __FILE__, __LINE__);
1098 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1099 strlen(bmsg->cm_fields['M']) + 1024 );
1100 strcat(bmsg->cm_fields['M'], addr);
1101 strcat(bmsg->cm_fields['M'], ": ");
1102 strcat(bmsg->cm_fields['M'], dsn);
1103 strcat(bmsg->cm_fields['M'], "\n");
1105 remove_token(instr, i, '\n');
1111 /* Deliver the bounce if there's anything worth mentioning */
1112 lprintf(9, "num_bounces = %d\n", num_bounces);
1113 if (num_bounces > 0) {
1115 /* First try the user who sent the message */
1116 lprintf(9, "bounce to user? <%s>\n", bounceto);
1117 if (strlen(bounceto) == 0) {
1118 lprintf(7, "No bounce address specified\n");
1119 bounce_msgid = (-1L);
1122 /* Can we deliver the bounce to the original sender? */
1123 valid = validate_recipients(bounceto);
1124 if (valid != NULL) {
1125 if (valid->num_error == 0) {
1126 CtdlSubmitMsg(bmsg, valid, "");
1127 successful_bounce = 1;
1131 /* If not, post it in the Aide> room */
1132 if (successful_bounce == 0) {
1133 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1136 /* Free up the memory we used */
1137 if (valid != NULL) {
1142 CtdlFreeMessage(bmsg);
1143 lprintf(9, "Done processing bounces\n");
1148 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1149 * set of delivery instructions for completed deliveries and remove them.
1151 * It returns the number of incomplete deliveries remaining.
1153 int smtp_purge_completed_deliveries(char *instr) {
1164 lines = num_tokens(instr, '\n');
1165 for (i=0; i<lines; ++i) {
1166 extract_token(buf, instr, i, '\n');
1167 extract(key, buf, 0);
1168 extract(addr, buf, 1);
1169 status = extract_int(buf, 2);
1170 extract(dsn, buf, 3);
1175 (!strcasecmp(key, "local"))
1176 || (!strcasecmp(key, "remote"))
1177 || (!strcasecmp(key, "ignet"))
1178 || (!strcasecmp(key, "room"))
1180 if (status == 2) completed = 1;
1185 remove_token(instr, i, '\n');
1198 * Called by smtp_do_queue() to handle an individual message.
1200 void smtp_do_procmsg(long msgnum, void *userdata) {
1201 struct CtdlMessage *msg;
1203 char *results = NULL;
1211 long text_msgid = (-1);
1212 int incomplete_deliveries_remaining;
1213 time_t attempted = 0L;
1214 time_t last_attempted = 0L;
1215 time_t retry = SMTP_RETRY_INTERVAL;
1217 lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1219 msg = CtdlFetchMessage(msgnum);
1221 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1225 instr = strdoop(msg->cm_fields['M']);
1226 CtdlFreeMessage(msg);
1228 /* Strip out the headers amd any other non-instruction line */
1229 lines = num_tokens(instr, '\n');
1230 for (i=0; i<lines; ++i) {
1231 extract_token(buf, instr, i, '\n');
1232 if (num_tokens(buf, '|') < 2) {
1233 remove_token(instr, i, '\n');
1239 /* Learn the message ID and find out about recent delivery attempts */
1240 lines = num_tokens(instr, '\n');
1241 for (i=0; i<lines; ++i) {
1242 extract_token(buf, instr, i, '\n');
1243 extract(key, buf, 0);
1244 if (!strcasecmp(key, "msgid")) {
1245 text_msgid = extract_long(buf, 1);
1247 if (!strcasecmp(key, "retry")) {
1248 /* double the retry interval after each attempt */
1249 retry = extract_long(buf, 1) * 2L;
1250 if (retry > SMTP_RETRY_MAX) {
1251 retry = SMTP_RETRY_MAX;
1253 remove_token(instr, i, '\n');
1255 if (!strcasecmp(key, "attempted")) {
1256 attempted = extract_long(buf, 1);
1257 if (attempted > last_attempted)
1258 last_attempted = attempted;
1263 * Postpone delivery if we've already tried recently.
1265 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1266 lprintf(7, "Retry time not yet reached.\n");
1273 * Bail out if there's no actual message associated with this
1275 if (text_msgid < 0L) {
1276 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1281 /* Plow through the instructions looking for 'remote' directives and
1282 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1283 * were experienced and it's time to try again)
1285 lines = num_tokens(instr, '\n');
1286 for (i=0; i<lines; ++i) {
1287 extract_token(buf, instr, i, '\n');
1288 extract(key, buf, 0);
1289 extract(addr, buf, 1);
1290 status = extract_int(buf, 2);
1291 extract(dsn, buf, 3);
1292 if ( (!strcasecmp(key, "remote"))
1293 && ((status==0)||(status==3)||(status==4)) ) {
1295 /* Remove this "remote" instruction from the set,
1296 * but replace the set's final newline if
1297 * remove_token() stripped it. It has to be there.
1299 remove_token(instr, i, '\n');
1300 if (instr[strlen(instr)-1] != '\n') {
1301 strcat(instr, "\n");
1306 lprintf(9, "SMTP: Trying <%s>\n", addr);
1307 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1309 if (results == NULL) {
1310 results = mallok(1024);
1311 memset(results, 0, 1024);
1314 results = reallok(results,
1315 strlen(results) + 1024);
1317 snprintf(&results[strlen(results)], 1024,
1319 key, addr, status, dsn);
1324 if (results != NULL) {
1325 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1326 strcat(instr, results);
1331 /* Generate 'bounce' messages */
1332 smtp_do_bounce(instr);
1334 /* Go through the delivery list, deleting completed deliveries */
1335 incomplete_deliveries_remaining =
1336 smtp_purge_completed_deliveries(instr);
1340 * No delivery instructions remain, so delete both the instructions
1341 * message and the message message.
1343 if (incomplete_deliveries_remaining <= 0) {
1344 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1345 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1350 * Uncompleted delivery instructions remain, so delete the old
1351 * instructions and replace with the updated ones.
1353 if (incomplete_deliveries_remaining > 0) {
1354 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1355 msg = mallok(sizeof(struct CtdlMessage));
1356 memset(msg, 0, sizeof(struct CtdlMessage));
1357 msg->cm_magic = CTDLMESSAGE_MAGIC;
1358 msg->cm_anon_type = MES_NORMAL;
1359 msg->cm_format_type = FMT_RFC822;
1360 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1361 snprintf(msg->cm_fields['M'],
1363 "Content-type: %s\n\n%s\n"
1366 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1368 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1369 CtdlFreeMessage(msg);
1379 * Run through the queue sending out messages.
1381 void smtp_do_queue(void) {
1382 static int doing_queue = 0;
1385 * This is a simple concurrency check to make sure only one queue run
1386 * is done at a time. We could do this with a mutex, but since we
1387 * don't really require extremely fine granularity here, we'll do it
1388 * with a static variable instead.
1390 if (doing_queue) return;
1394 * Go ahead and run the queue
1396 lprintf(7, "SMTP: processing outbound queue\n");
1398 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1399 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1402 CtdlForEachMessage(MSGS_ALL, 0L,
1403 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1405 lprintf(7, "SMTP: queue run completed\n");
1412 /*****************************************************************************/
1413 /* SMTP UTILITY COMMANDS */
1414 /*****************************************************************************/
1416 void cmd_smtp(char *argbuf) {
1423 if (CtdlAccessCheck(ac_aide)) return;
1425 extract(cmd, argbuf, 0);
1427 if (!strcasecmp(cmd, "mx")) {
1428 extract(node, argbuf, 1);
1429 num_mxhosts = getmx(buf, node);
1430 cprintf("%d %d MX hosts listed for %s\n",
1431 LISTING_FOLLOWS, num_mxhosts, node);
1432 for (i=0; i<num_mxhosts; ++i) {
1433 extract(node, buf, i);
1434 cprintf("%s\n", node);
1440 else if (!strcasecmp(cmd, "runqueue")) {
1442 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1447 cprintf("%d Invalid command.\n", ERROR+ILLEGAL_VALUE);
1454 * Initialize the SMTP outbound queue
1456 void smtp_init_spoolout(void) {
1457 struct ctdlroom qrbuf;
1460 * Create the room. This will silently fail if the room already
1461 * exists, and that's perfectly ok, because we want it to exist.
1463 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0);
1466 * Make sure it's set to be a "system room" so it doesn't show up
1467 * in the <K>nown rooms list for Aides.
1469 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1470 qrbuf.QRflags2 |= QR2_SYSTEM;
1478 /*****************************************************************************/
1479 /* MODULE INITIALIZATION STUFF */
1480 /*****************************************************************************/
1483 char *serv_smtp_init(void)
1485 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1490 CtdlRegisterServiceHook(0, /* ...and locally */
1495 smtp_init_spoolout();
1496 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1497 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");