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 /* Otherwise, make sure outsiders aren't trying to forge mail from
415 process_rfc822_addr(SMTP->from, user, node, name);
416 if (CtdlHostAlias(node) != hostalias_nomatch) {
418 "You must log in to send mail from %s\r\n",
420 strcpy(SMTP->from, "");
425 cprintf("250 2.0.0 Sender ok\r\n");
431 * Implements the "RCPT To:" command
433 void smtp_rcpt(char *argbuf) {
435 char message_to_spammer[SIZ];
436 struct recptypes *valid = NULL;
438 if (strlen(SMTP->from) == 0) {
439 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
443 if (strncasecmp(argbuf, "To:", 3)) {
444 cprintf("501 5.1.7 Syntax error\r\n");
448 strcpy(recp, &argbuf[3]);
450 stripallbut(recp, '<', '>');
452 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
453 cprintf("452 4.5.3 Too many recipients\r\n");
458 if ( (!CC->logged_in)
459 && (!SMTP->is_lmtp) ) {
460 if (rbl_check(message_to_spammer)) {
461 cprintf("550 %s\r\n", message_to_spammer);
462 /* no need to phree(valid), it's not allocated yet */
467 valid = validate_recipients(recp);
468 if (valid->num_error > 0) {
469 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
474 if (valid->num_internet > 0) {
475 if ( (SMTP->message_originated_locally == 0)
476 && (SMTP->is_lmtp == 0) ) {
477 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
483 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
484 if (strlen(SMTP->recipients) > 0) {
485 strcat(SMTP->recipients, ",");
487 strcat(SMTP->recipients, recp);
488 SMTP->number_of_recipients += 1;
495 * Implements the DATA command
497 void smtp_data(void) {
499 struct CtdlMessage *msg;
502 struct recptypes *valid;
507 if (strlen(SMTP->from) == 0) {
508 cprintf("503 5.5.1 Need MAIL command first.\r\n");
512 if (SMTP->number_of_recipients < 1) {
513 cprintf("503 5.5.1 Need RCPT command first.\r\n");
517 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
519 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
522 if (body != NULL) snprintf(body, 4096,
523 "Received: from %s (%s [%s])\n"
531 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
534 "Unable to save message: internal error.\r\n");
538 lprintf(9, "Converting message...\n");
539 msg = convert_internet_message(body);
541 /* If the user is locally authenticated, FORCE the From: header to
542 * show up as the real sender. Yes, this violates the RFC standard,
543 * but IT MAKES SENSE. If you prefer strict RFC adherence over
544 * common sense, you can disable this in the configuration.
546 * We also set the "message room name" ('O' field) to MAILROOM
547 * (which is Mail> on most systems) to prevent it from getting set
548 * to something ugly like "0000058008.Sent Items>" when the message
549 * is read with a Citadel client.
551 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
552 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
553 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
554 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
555 if (msg->cm_fields['F'] != NULL) phree(msg->cm_fields['F']);
556 if (msg->cm_fields['O'] != NULL) phree(msg->cm_fields['O']);
557 msg->cm_fields['A'] = strdoop(CC->user.fullname);
558 msg->cm_fields['N'] = strdoop(config.c_nodename);
559 msg->cm_fields['H'] = strdoop(config.c_humannode);
560 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
561 msg->cm_fields['O'] = strdoop(MAILROOM);
564 /* Submit the message into the Citadel system. */
565 valid = validate_recipients(SMTP->recipients);
567 /* If there are modules that want to scan this message before final
568 * submission (such as virus checkers or spam filters), call them now
569 * and give them an opportunity to reject the message.
571 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
573 if (scan_errors > 0) { /* We don't want this message! */
575 if (msg->cm_fields['0'] == NULL) {
576 msg->cm_fields['0'] = strdoop(
577 "5.7.1 Message rejected by filter");
580 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
583 else { /* Ok, we'll accept this message. */
584 msgnum = CtdlSubmitMsg(msg, valid, "");
586 sprintf(result, "250 2.0.0 Message accepted.\r\n");
589 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
593 /* For SMTP and ESTMP, just print the result message. For LMTP, we
594 * have to print one result message for each recipient. Since there
595 * is nothing in Citadel which would cause different recipients to
596 * have different results, we can get away with just spitting out the
597 * same message once for each recipient.
600 for (i=0; i<SMTP->number_of_recipients; ++i) {
601 cprintf("%s", result);
605 cprintf("%s", result);
608 CtdlFreeMessage(msg);
610 smtp_data_clear(); /* clear out the buffers now */
617 * Main command loop for SMTP sessions.
619 void smtp_command_loop(void) {
623 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
624 if (client_gets(cmdbuf) < 1) {
625 lprintf(3, "SMTP socket is broken. Ending session.\n");
629 lprintf(5, "SMTP: %s\n", cmdbuf);
630 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
632 lprintf(9, "CC->logged_in = %d\n", CC->logged_in);
634 if (SMTP->command_state == smtp_user) {
635 smtp_get_user(cmdbuf);
638 else if (SMTP->command_state == smtp_password) {
639 smtp_get_pass(cmdbuf);
642 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
643 smtp_auth(&cmdbuf[5]);
646 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
650 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
651 smtp_expn(&cmdbuf[5]);
654 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
655 smtp_hello(&cmdbuf[5], 0);
658 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
659 smtp_hello(&cmdbuf[5], 1);
662 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
663 smtp_hello(&cmdbuf[5], 2);
666 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
670 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
671 smtp_mail(&cmdbuf[5]);
674 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
675 cprintf("250 NOOP\r\n");
678 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
679 cprintf("221 Goodbye...\r\n");
684 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
685 smtp_rcpt(&cmdbuf[5]);
688 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
692 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
693 smtp_vrfy(&cmdbuf[5]);
697 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
705 /*****************************************************************************/
706 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
707 /*****************************************************************************/
714 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
717 void smtp_try(const char *key, const char *addr, int *status,
718 char *dsn, size_t n, long msgnum)
725 char user[SIZ], node[SIZ], name[SIZ];
731 size_t blocksize = 0;
734 /* Parse out the host portion of the recipient address */
735 process_rfc822_addr(addr, user, node, name);
737 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
740 /* Load the message out of the database into a temp file */
742 if (msg_fp == NULL) {
744 snprintf(dsn, n, "Error creating temporary file");
748 CtdlRedirectOutput(msg_fp, -1);
749 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
750 CtdlRedirectOutput(NULL, -1);
751 fseek(msg_fp, 0L, SEEK_END);
752 msg_size = ftell(msg_fp);
756 /* Extract something to send later in the 'MAIL From:' command */
757 strcpy(mailfrom, "");
761 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
762 if (!strncasecmp(buf, "From:", 5)) {
763 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
765 for (i=0; i<strlen(mailfrom); ++i) {
766 if (!isprint(mailfrom[i])) {
767 strcpy(&mailfrom[i], &mailfrom[i+1]);
772 /* Strip out parenthesized names */
775 for (i=0; i<strlen(mailfrom); ++i) {
776 if (mailfrom[i] == '(') lp = i;
777 if (mailfrom[i] == ')') rp = i;
779 if ((lp>0)&&(rp>lp)) {
780 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
783 /* Prefer brokketized names */
786 for (i=0; i<strlen(mailfrom); ++i) {
787 if (mailfrom[i] == '<') lp = i;
788 if (mailfrom[i] == '>') rp = i;
790 if ( (lp>=0) && (rp>lp) ) {
792 strcpy(mailfrom, &mailfrom[lp]);
797 } while (scan_done == 0);
798 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
800 /* Figure out what mail exchanger host we have to connect to */
801 num_mxhosts = getmx(mxhosts, node);
802 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
803 if (num_mxhosts < 1) {
805 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
810 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
811 extract(buf, mxhosts, mx);
812 lprintf(9, "Trying <%s>\n", buf);
813 sock = sock_connect(buf, "25", "tcp");
814 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
815 if (sock >= 0) lprintf(9, "Connected!\n");
816 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
820 *status = 4; /* dsn is already filled in */
824 /* Process the SMTP greeting from the server */
825 if (ml_sock_gets(sock, buf) < 0) {
827 strcpy(dsn, "Connection broken during SMTP conversation");
830 lprintf(9, "<%s\n", buf);
834 safestrncpy(dsn, &buf[4], 1023);
839 safestrncpy(dsn, &buf[4], 1023);
844 /* At this point we know we are talking to a real SMTP server */
846 /* Do a HELO command */
847 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
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 HELO");
855 lprintf(9, "<%s\n", buf);
859 safestrncpy(dsn, &buf[4], 1023);
864 safestrncpy(dsn, &buf[4], 1023);
870 /* HELO succeeded, now try the MAIL From: command */
871 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
872 lprintf(9, ">%s", buf);
873 sock_write(sock, buf, strlen(buf));
874 if (ml_sock_gets(sock, buf) < 0) {
876 strcpy(dsn, "Connection broken during SMTP MAIL");
879 lprintf(9, "<%s\n", buf);
883 safestrncpy(dsn, &buf[4], 1023);
888 safestrncpy(dsn, &buf[4], 1023);
894 /* MAIL succeeded, now try the RCPT To: command */
895 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
896 lprintf(9, ">%s", buf);
897 sock_write(sock, buf, strlen(buf));
898 if (ml_sock_gets(sock, buf) < 0) {
900 strcpy(dsn, "Connection broken during SMTP RCPT");
903 lprintf(9, "<%s\n", buf);
907 safestrncpy(dsn, &buf[4], 1023);
912 safestrncpy(dsn, &buf[4], 1023);
918 /* RCPT succeeded, now try the DATA command */
919 lprintf(9, ">DATA\n");
920 sock_write(sock, "DATA\r\n", 6);
921 if (ml_sock_gets(sock, buf) < 0) {
923 strcpy(dsn, "Connection broken during SMTP DATA");
926 lprintf(9, "<%s\n", buf);
930 safestrncpy(dsn, &buf[4], 1023);
935 safestrncpy(dsn, &buf[4], 1023);
940 /* If we reach this point, the server is expecting data */
942 while (msg_size > 0) {
943 blocksize = sizeof(buf);
944 if (blocksize > msg_size) blocksize = msg_size;
945 fread(buf, blocksize, 1, msg_fp);
946 sock_write(sock, buf, blocksize);
947 msg_size -= blocksize;
949 if (buf[blocksize-1] != 10) {
950 lprintf(5, "Possible problem: message did not correctly "
951 "terminate. (expecting 0x10, got 0x%02x)\n",
955 sock_write(sock, ".\r\n", 3);
956 if (ml_sock_gets(sock, buf) < 0) {
958 strcpy(dsn, "Connection broken during SMTP message transmit");
961 lprintf(9, "%s\n", buf);
965 safestrncpy(dsn, &buf[4], 1023);
970 safestrncpy(dsn, &buf[4], 1023);
976 safestrncpy(dsn, &buf[4], 1023);
979 lprintf(9, ">QUIT\n");
980 sock_write(sock, "QUIT\r\n", 6);
981 ml_sock_gets(sock, buf);
982 lprintf(9, "<%s\n", buf);
984 bail: if (msg_fp != NULL) fclose(msg_fp);
992 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
993 * instructions for "5" codes (permanent fatal errors) and produce/deliver
994 * a "bounce" message (delivery status notification).
996 void smtp_do_bounce(char *instr) {
1004 char bounceto[1024];
1005 int num_bounces = 0;
1006 int bounce_this = 0;
1007 long bounce_msgid = (-1);
1008 time_t submitted = 0L;
1009 struct CtdlMessage *bmsg = NULL;
1011 struct recptypes *valid;
1012 int successful_bounce = 0;
1014 lprintf(9, "smtp_do_bounce() called\n");
1015 strcpy(bounceto, "");
1017 lines = num_tokens(instr, '\n');
1020 /* See if it's time to give up on delivery of this message */
1021 for (i=0; i<lines; ++i) {
1022 extract_token(buf, instr, i, '\n');
1023 extract(key, buf, 0);
1024 extract(addr, buf, 1);
1025 if (!strcasecmp(key, "submitted")) {
1026 submitted = atol(addr);
1030 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1036 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1037 if (bmsg == NULL) return;
1038 memset(bmsg, 0, sizeof(struct CtdlMessage));
1040 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1041 bmsg->cm_anon_type = MES_NORMAL;
1042 bmsg->cm_format_type = 1;
1043 bmsg->cm_fields['A'] = strdoop("Citadel");
1044 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1045 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1047 if (give_up) bmsg->cm_fields['M'] = strdoop(
1048 "A message you sent could not be delivered to some or all of its recipients\n"
1049 "due to prolonged unavailability of its destination(s).\n"
1050 "Giving up on the following addresses:\n\n"
1053 else bmsg->cm_fields['M'] = strdoop(
1054 "A message you sent could not be delivered to some or all of its recipients.\n"
1055 "The following addresses were undeliverable:\n\n"
1059 * Now go through the instructions checking for stuff.
1061 for (i=0; i<lines; ++i) {
1062 extract_token(buf, instr, i, '\n');
1063 extract(key, buf, 0);
1064 extract(addr, buf, 1);
1065 status = extract_int(buf, 2);
1066 extract(dsn, buf, 3);
1069 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1070 key, addr, status, dsn);
1072 if (!strcasecmp(key, "bounceto")) {
1073 strcpy(bounceto, addr);
1077 (!strcasecmp(key, "local"))
1078 || (!strcasecmp(key, "remote"))
1079 || (!strcasecmp(key, "ignet"))
1080 || (!strcasecmp(key, "room"))
1082 if (status == 5) bounce_this = 1;
1083 if (give_up) bounce_this = 1;
1089 if (bmsg->cm_fields['M'] == NULL) {
1090 lprintf(2, "ERROR ... M field is null "
1091 "(%s:%d)\n", __FILE__, __LINE__);
1094 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1095 strlen(bmsg->cm_fields['M']) + 1024 );
1096 strcat(bmsg->cm_fields['M'], addr);
1097 strcat(bmsg->cm_fields['M'], ": ");
1098 strcat(bmsg->cm_fields['M'], dsn);
1099 strcat(bmsg->cm_fields['M'], "\n");
1101 remove_token(instr, i, '\n');
1107 /* Deliver the bounce if there's anything worth mentioning */
1108 lprintf(9, "num_bounces = %d\n", num_bounces);
1109 if (num_bounces > 0) {
1111 /* First try the user who sent the message */
1112 lprintf(9, "bounce to user? <%s>\n", bounceto);
1113 if (strlen(bounceto) == 0) {
1114 lprintf(7, "No bounce address specified\n");
1115 bounce_msgid = (-1L);
1118 /* Can we deliver the bounce to the original sender? */
1119 valid = validate_recipients(bounceto);
1120 if (valid != NULL) {
1121 if (valid->num_error == 0) {
1122 CtdlSubmitMsg(bmsg, valid, "");
1123 successful_bounce = 1;
1127 /* If not, post it in the Aide> room */
1128 if (successful_bounce == 0) {
1129 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1132 /* Free up the memory we used */
1133 if (valid != NULL) {
1138 CtdlFreeMessage(bmsg);
1139 lprintf(9, "Done processing bounces\n");
1144 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1145 * set of delivery instructions for completed deliveries and remove them.
1147 * It returns the number of incomplete deliveries remaining.
1149 int smtp_purge_completed_deliveries(char *instr) {
1160 lines = num_tokens(instr, '\n');
1161 for (i=0; i<lines; ++i) {
1162 extract_token(buf, instr, i, '\n');
1163 extract(key, buf, 0);
1164 extract(addr, buf, 1);
1165 status = extract_int(buf, 2);
1166 extract(dsn, buf, 3);
1171 (!strcasecmp(key, "local"))
1172 || (!strcasecmp(key, "remote"))
1173 || (!strcasecmp(key, "ignet"))
1174 || (!strcasecmp(key, "room"))
1176 if (status == 2) completed = 1;
1181 remove_token(instr, i, '\n');
1194 * Called by smtp_do_queue() to handle an individual message.
1196 void smtp_do_procmsg(long msgnum, void *userdata) {
1197 struct CtdlMessage *msg;
1199 char *results = NULL;
1207 long text_msgid = (-1);
1208 int incomplete_deliveries_remaining;
1209 time_t attempted = 0L;
1210 time_t last_attempted = 0L;
1211 time_t retry = SMTP_RETRY_INTERVAL;
1213 lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1215 msg = CtdlFetchMessage(msgnum);
1217 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1221 instr = strdoop(msg->cm_fields['M']);
1222 CtdlFreeMessage(msg);
1224 /* Strip out the headers amd any other non-instruction line */
1225 lines = num_tokens(instr, '\n');
1226 for (i=0; i<lines; ++i) {
1227 extract_token(buf, instr, i, '\n');
1228 if (num_tokens(buf, '|') < 2) {
1229 remove_token(instr, i, '\n');
1235 /* Learn the message ID and find out about recent delivery attempts */
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 if (!strcasecmp(key, "msgid")) {
1241 text_msgid = extract_long(buf, 1);
1243 if (!strcasecmp(key, "retry")) {
1244 /* double the retry interval after each attempt */
1245 retry = extract_long(buf, 1) * 2L;
1246 if (retry > SMTP_RETRY_MAX) {
1247 retry = SMTP_RETRY_MAX;
1249 remove_token(instr, i, '\n');
1251 if (!strcasecmp(key, "attempted")) {
1252 attempted = extract_long(buf, 1);
1253 if (attempted > last_attempted)
1254 last_attempted = attempted;
1259 * Postpone delivery if we've already tried recently.
1261 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1262 lprintf(7, "Retry time not yet reached.\n");
1269 * Bail out if there's no actual message associated with this
1271 if (text_msgid < 0L) {
1272 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1277 /* Plow through the instructions looking for 'remote' directives and
1278 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1279 * were experienced and it's time to try again)
1281 lines = num_tokens(instr, '\n');
1282 for (i=0; i<lines; ++i) {
1283 extract_token(buf, instr, i, '\n');
1284 extract(key, buf, 0);
1285 extract(addr, buf, 1);
1286 status = extract_int(buf, 2);
1287 extract(dsn, buf, 3);
1288 if ( (!strcasecmp(key, "remote"))
1289 && ((status==0)||(status==3)||(status==4)) ) {
1291 /* Remove this "remote" instruction from the set,
1292 * but replace the set's final newline if
1293 * remove_token() stripped it. It has to be there.
1295 remove_token(instr, i, '\n');
1296 if (instr[strlen(instr)-1] != '\n') {
1297 strcat(instr, "\n");
1302 lprintf(9, "SMTP: Trying <%s>\n", addr);
1303 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1305 if (results == NULL) {
1306 results = mallok(1024);
1307 memset(results, 0, 1024);
1310 results = reallok(results,
1311 strlen(results) + 1024);
1313 snprintf(&results[strlen(results)], 1024,
1315 key, addr, status, dsn);
1320 if (results != NULL) {
1321 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1322 strcat(instr, results);
1327 /* Generate 'bounce' messages */
1328 smtp_do_bounce(instr);
1330 /* Go through the delivery list, deleting completed deliveries */
1331 incomplete_deliveries_remaining =
1332 smtp_purge_completed_deliveries(instr);
1336 * No delivery instructions remain, so delete both the instructions
1337 * message and the message message.
1339 if (incomplete_deliveries_remaining <= 0) {
1340 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1341 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1346 * Uncompleted delivery instructions remain, so delete the old
1347 * instructions and replace with the updated ones.
1349 if (incomplete_deliveries_remaining > 0) {
1350 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1351 msg = mallok(sizeof(struct CtdlMessage));
1352 memset(msg, 0, sizeof(struct CtdlMessage));
1353 msg->cm_magic = CTDLMESSAGE_MAGIC;
1354 msg->cm_anon_type = MES_NORMAL;
1355 msg->cm_format_type = FMT_RFC822;
1356 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1357 snprintf(msg->cm_fields['M'],
1359 "Content-type: %s\n\n%s\n"
1362 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1364 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1365 CtdlFreeMessage(msg);
1375 * Run through the queue sending out messages.
1377 void smtp_do_queue(void) {
1378 static int doing_queue = 0;
1381 * This is a simple concurrency check to make sure only one queue run
1382 * is done at a time. We could do this with a mutex, but since we
1383 * don't really require extremely fine granularity here, we'll do it
1384 * with a static variable instead.
1386 if (doing_queue) return;
1390 * Go ahead and run the queue
1392 lprintf(7, "SMTP: processing outbound queue\n");
1394 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1395 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1398 CtdlForEachMessage(MSGS_ALL, 0L,
1399 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1401 lprintf(7, "SMTP: queue run completed\n");
1408 /*****************************************************************************/
1409 /* SMTP UTILITY COMMANDS */
1410 /*****************************************************************************/
1412 void cmd_smtp(char *argbuf) {
1419 if (CtdlAccessCheck(ac_aide)) return;
1421 extract(cmd, argbuf, 0);
1423 if (!strcasecmp(cmd, "mx")) {
1424 extract(node, argbuf, 1);
1425 num_mxhosts = getmx(buf, node);
1426 cprintf("%d %d MX hosts listed for %s\n",
1427 LISTING_FOLLOWS, num_mxhosts, node);
1428 for (i=0; i<num_mxhosts; ++i) {
1429 extract(node, buf, i);
1430 cprintf("%s\n", node);
1436 else if (!strcasecmp(cmd, "runqueue")) {
1438 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1443 cprintf("%d Invalid command.\n", ERROR+ILLEGAL_VALUE);
1450 * Initialize the SMTP outbound queue
1452 void smtp_init_spoolout(void) {
1453 struct ctdlroom qrbuf;
1456 * Create the room. This will silently fail if the room already
1457 * exists, and that's perfectly ok, because we want it to exist.
1459 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0);
1462 * Make sure it's set to be a "system room" so it doesn't show up
1463 * in the <K>nown rooms list for Aides.
1465 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1466 qrbuf.QRflags2 |= QR2_SYSTEM;
1474 /*****************************************************************************/
1475 /* MODULE INITIALIZATION STUFF */
1476 /*****************************************************************************/
1479 char *serv_smtp_init(void)
1481 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1486 CtdlRegisterServiceHook(0, /* ...and locally */
1491 smtp_init_spoolout();
1492 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1493 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");