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>
36 #include "sysdep_decls.h"
37 #include "citserver.h"
41 #include "dynloader.h"
48 #include "internet_addressing.h"
51 #include "clientsocket.h"
58 struct citsmtp { /* Information about the current session */
61 struct usersupp vrfy_buffer;
65 int number_of_recipients;
68 int message_originated_locally;
69 struct recptypes valid;
72 enum { /* Command states for login authentication */
78 enum { /* Delivery modes */
83 #define SMTP ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
84 #define SMTP_RECPS ((char *)CtdlGetUserData(SYM_SMTP_RECPS))
85 #define SMTP_ROOMS ((char *)CtdlGetUserData(SYM_SMTP_ROOMS))
91 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
95 /*****************************************************************************/
96 /* SMTP SERVER (INBOUND) STUFF */
97 /*****************************************************************************/
103 * Here's where our SMTP session begins its happy day.
105 void smtp_greeting(void) {
107 strcpy(CC->cs_clientname, "SMTP session");
108 CC->internal_pgm = 1;
109 CC->cs_flags |= CS_STEALTH;
110 CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
111 CtdlAllocUserData(SYM_SMTP_RECPS, SIZ);
112 CtdlAllocUserData(SYM_SMTP_ROOMS, SIZ);
113 sprintf(SMTP_RECPS, "%s", "");
114 sprintf(SMTP_ROOMS, "%s", "");
116 cprintf("220 %s ESMTP Citadel/UX server ready.\r\n", config.c_fqdn);
121 * Implement HELO and EHLO commands.
123 void smtp_hello(char *argbuf, int is_esmtp) {
125 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
128 cprintf("250 Greetings and joyous salutations.\r\n");
131 cprintf("250-Greetings and joyous salutations.\r\n");
132 cprintf("250-HELP\r\n");
133 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
134 cprintf("250-PIPELINING\r\n");
135 cprintf("250 AUTH=LOGIN\r\n");
141 * Implement HELP command.
143 void smtp_help(void) {
144 cprintf("214-Commands accepted:\r\n");
145 cprintf("214- DATA\r\n");
146 cprintf("214- EHLO\r\n");
147 cprintf("214- EXPN\r\n");
148 cprintf("214- HELO\r\n");
149 cprintf("214- HELP\r\n");
150 cprintf("214- MAIL\r\n");
151 cprintf("214- NOOP\r\n");
152 cprintf("214- QUIT\r\n");
153 cprintf("214- RCPT\r\n");
154 cprintf("214- RSET\r\n");
155 cprintf("214- VRFY\r\n");
163 void smtp_get_user(char *argbuf) {
167 decode_base64(username, argbuf, SIZ);
168 lprintf(9, "Trying <%s>\n", username);
169 if (CtdlLoginExistingUser(username) == login_ok) {
170 encode_base64(buf, "Password:");
171 cprintf("334 %s\r\n", buf);
172 SMTP->command_state = smtp_password;
175 cprintf("500 No such user.\r\n");
176 SMTP->command_state = smtp_command;
184 void smtp_get_pass(char *argbuf) {
187 decode_base64(password, argbuf, SIZ);
188 lprintf(9, "Trying <%s>\n", password);
189 if (CtdlTryPassword(password) == pass_ok) {
190 cprintf("235 Authentication successful.\r\n");
191 lprintf(9, "SMTP authenticated login successful\n");
192 CC->internal_pgm = 0;
193 CC->cs_flags &= ~CS_STEALTH;
196 cprintf("500 Authentication failed.\r\n");
198 SMTP->command_state = smtp_command;
205 void smtp_auth(char *argbuf) {
208 if (strncasecmp(argbuf, "login", 5) ) {
209 cprintf("550 We only support LOGIN authentication.\r\n");
213 if (strlen(argbuf) >= 7) {
214 smtp_get_user(&argbuf[6]);
218 encode_base64(buf, "Username:");
219 cprintf("334 %s\r\n", buf);
220 SMTP->command_state = smtp_user;
226 * Back end for smtp_vrfy() command
228 void smtp_vrfy_backend(struct usersupp *us, void *data) {
230 if (!fuzzy_match(us, SMTP->vrfy_match)) {
232 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
238 * Implements the VRFY (verify user name) command.
239 * Performs fuzzy match on full user names.
241 void smtp_vrfy(char *argbuf) {
242 SMTP->vrfy_count = 0;
243 strcpy(SMTP->vrfy_match, argbuf);
244 ForEachUser(smtp_vrfy_backend, NULL);
246 if (SMTP->vrfy_count < 1) {
247 cprintf("550 String does not match anything.\r\n");
249 else if (SMTP->vrfy_count == 1) {
250 cprintf("250 %s <cit%ld@%s>\r\n",
251 SMTP->vrfy_buffer.fullname,
252 SMTP->vrfy_buffer.usernum,
255 else if (SMTP->vrfy_count > 1) {
256 cprintf("553 Request ambiguous: %d users matched.\r\n",
265 * Back end for smtp_expn() command
267 void smtp_expn_backend(struct usersupp *us, void *data) {
269 if (!fuzzy_match(us, SMTP->vrfy_match)) {
271 if (SMTP->vrfy_count >= 1) {
272 cprintf("250-%s <cit%ld@%s>\r\n",
273 SMTP->vrfy_buffer.fullname,
274 SMTP->vrfy_buffer.usernum,
279 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
285 * Implements the EXPN (expand user name) command.
286 * Performs fuzzy match on full user names.
288 void smtp_expn(char *argbuf) {
289 SMTP->vrfy_count = 0;
290 strcpy(SMTP->vrfy_match, argbuf);
291 ForEachUser(smtp_expn_backend, NULL);
293 if (SMTP->vrfy_count < 1) {
294 cprintf("550 String does not match anything.\r\n");
296 else if (SMTP->vrfy_count >= 1) {
297 cprintf("250 %s <cit%ld@%s>\r\n",
298 SMTP->vrfy_buffer.fullname,
299 SMTP->vrfy_buffer.usernum,
306 * Implements the RSET (reset state) command.
307 * Currently this just zeroes out the state buffer. If pointers to data
308 * allocated with mallok() are ever placed in the state buffer, we have to
309 * be sure to phree() them first!
311 void smtp_rset(void) {
312 memset(SMTP, 0, sizeof(struct citsmtp));
316 cprintf("250 Zap!\r\n");
320 * Clear out the portions of the state buffer that need to be cleared out
321 * after the DATA command finishes.
323 void smtp_data_clear(void) {
324 strcpy(SMTP->from, "");
325 SMTP->number_of_recipients = 0;
326 SMTP->delivery_mode = 0;
327 SMTP->message_originated_locally = 0;
328 memset(&SMTP->valid, 0, sizeof(struct recptypes));
334 * Implements the "MAIL From:" command
336 void smtp_mail(char *argbuf) {
341 if (strlen(SMTP->from) != 0) {
342 cprintf("503 Only one sender permitted\r\n");
346 if (strncasecmp(argbuf, "From:", 5)) {
347 cprintf("501 Syntax error\r\n");
351 strcpy(SMTP->from, &argbuf[5]);
354 if (strlen(SMTP->from) == 0) {
355 cprintf("501 Empty sender name is not permitted\r\n");
360 /* If this SMTP connection is from a logged-in user, make sure that
361 * the user only sends email from his/her own address.
364 cvt = convert_internet_address(user, node, SMTP->from);
365 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
366 if ( (cvt != 0) || (strcasecmp(user, CC->usersupp.fullname))) {
367 cprintf("550 <%s> is not your address.\r\n",
369 strcpy(SMTP->from, "");
373 SMTP->message_originated_locally = 1;
377 /* Otherwise, make sure outsiders aren't trying to forge mail from
381 cvt = convert_internet_address(user, node, SMTP->from);
382 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
383 if (CtdlHostAlias(node) == hostalias_localhost) {
384 cprintf("550 You must log in to send mail from %s\r\n",
386 strcpy(SMTP->from, "");
391 cprintf("250 Sender ok\r\n");
397 * Implements the "RCPT To:" command
399 void smtp_rcpt(char *argbuf) {
405 if (strlen(SMTP->from) == 0) {
406 cprintf("503 Need MAIL before RCPT\r\n");
410 if (strncasecmp(argbuf, "To:", 3)) {
411 cprintf("501 Syntax error\r\n");
415 strcpy(recp, &argbuf[3]);
419 cvt = convert_internet_address(user, node, recp);
420 snprintf(recp, sizeof recp, "%s@%s", user, node);
421 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
423 /* FIXME possible buffer overflow type of issues here. */
425 case rfc822_address_locally_validated:
426 cprintf("250 %s is a valid local user.\r\n", user);
427 if (SMTP->valid.num_local > 0) {
428 strcat(SMTP->valid.recp_local, "|");
430 strcat(SMTP->valid.recp_local, user);
431 SMTP->valid.num_local += 1;
432 SMTP->number_of_recipients += 1;
435 case rfc822_room_delivery:
436 cprintf("250 Delivering to room '%s'\r\n", user);
437 cprintf("250 %s is a valid recipient.\r\n", user);
438 if (SMTP->valid.num_room > 0) {
439 strcat(SMTP->valid.recp_room, "|");
441 strcat(SMTP->valid.recp_room, user);
442 SMTP->valid.num_room += 1;
443 SMTP->number_of_recipients += 1;
446 case rfc822_address_on_citadel_network:
447 cprintf("250 '%s' is a valid network user.\r\n", user);
448 if (SMTP->valid.num_ignet > 0) {
449 strcat(SMTP->valid.recp_ignet, "|");
451 strcat(SMTP->valid.recp_ignet, user);
452 SMTP->valid.num_ignet += 1;
453 SMTP->number_of_recipients += 1;
456 case rfc822_no_such_user:
457 cprintf("550 %s: no such user\r\n", recp);
460 case rfc822_address_nonlocal:
461 if (SMTP->message_originated_locally == 0) {
462 cprintf("551 Relaying denied.\r\n");
465 cprintf("250 Remote recipient %s ok\r\n", recp);
466 SMTP->number_of_recipients += 1;
472 cprintf("599 Unknown error\r\n");
479 * Implements the DATA command
481 void smtp_data(void) {
483 struct CtdlMessage *msg;
487 if (strlen(SMTP->from) == 0) {
488 cprintf("503 Need MAIL command first.\r\n");
492 if (SMTP->number_of_recipients < 1) {
493 cprintf("503 Need RCPT command first.\r\n");
497 cprintf("354 Transmit message now; terminate with '.' by itself\r\n");
499 datestring(nowstamp, time(NULL), DATESTRING_RFC822);
502 if (body != NULL) snprintf(body, 4096,
503 "Received: from %s\n"
510 body = CtdlReadMessageBody(".", config.c_maxmsglen, body);
512 cprintf("550 Unable to save message: internal error.\r\n");
516 lprintf(9, "Converting message...\n");
517 msg = convert_internet_message(body);
519 /* If the user is locally authenticated, FORCE the From: header to
520 * show up as the real sender. Yes, this violates the RFC standard,
521 * but IT MAKES SENSE. Comment it out if you don't like this behavior.
524 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
525 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
526 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
527 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
528 msg->cm_fields['N'] = strdoop(config.c_nodename);
529 msg->cm_fields['H'] = strdoop(config.c_humannode);
532 /* Submit the message into the Citadel system. */
533 msgnum = CtdlSubmitMsg(msg, &SMTP->valid, "");
534 CtdlFreeMessage(msg);
537 cprintf("250 Message accepted.\r\n");
540 cprintf("550 Internal delivery error\r\n");
543 smtp_data_clear(); /* clear out the buffers now */
550 * Main command loop for SMTP sessions.
552 void smtp_command_loop(void) {
556 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
557 if (client_gets(cmdbuf) < 1) {
558 lprintf(3, "SMTP socket is broken. Ending session.\n");
562 lprintf(5, "citserver[%3d]: %s\n", CC->cs_pid, cmdbuf);
563 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
565 if (SMTP->command_state == smtp_user) {
566 smtp_get_user(cmdbuf);
569 else if (SMTP->command_state == smtp_password) {
570 smtp_get_pass(cmdbuf);
573 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
574 smtp_auth(&cmdbuf[5]);
577 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
581 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
582 smtp_hello(&cmdbuf[5], 1);
585 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
586 smtp_expn(&cmdbuf[5]);
589 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
590 smtp_hello(&cmdbuf[5], 0);
593 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
597 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
598 smtp_mail(&cmdbuf[5]);
601 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
602 cprintf("250 NOOP\r\n");
605 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
606 cprintf("221 Goodbye...\r\n");
611 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
612 smtp_rcpt(&cmdbuf[5]);
615 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
619 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
620 smtp_vrfy(&cmdbuf[5]);
624 cprintf("502 I'm afraid I can't do that.\r\n");
632 /*****************************************************************************/
633 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
634 /*****************************************************************************/
641 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
644 void smtp_try(char *key, char *addr, int *status, char *dsn, long msgnum)
651 char user[SIZ], node[SIZ], name[SIZ];
657 size_t blocksize = 0;
660 /* Parse out the host portion of the recipient address */
661 process_rfc822_addr(addr, user, node, name);
663 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
666 /* Load the message out of the database into a temp file */
668 if (msg_fp == NULL) {
670 sprintf(dsn, "Error creating temporary file");
674 CtdlRedirectOutput(msg_fp, -1);
675 CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, 1);
676 CtdlRedirectOutput(NULL, -1);
677 fseek(msg_fp, 0L, SEEK_END);
678 msg_size = ftell(msg_fp);
682 /* Extract something to send later in the 'MAIL From:' command */
683 strcpy(mailfrom, "");
687 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
688 if (!strncasecmp(buf, "From:", 5)) {
689 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
691 for (i=0; i<strlen(mailfrom); ++i) {
692 if (!isprint(mailfrom[i])) {
693 strcpy(&mailfrom[i], &mailfrom[i+1]);
698 /* Strip out parenthesized names */
701 for (i=0; i<strlen(mailfrom); ++i) {
702 if (mailfrom[i] == '(') lp = i;
703 if (mailfrom[i] == ')') rp = i;
705 if ((lp>0)&&(rp>lp)) {
706 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
709 /* Prefer brokketized names */
712 for (i=0; i<strlen(mailfrom); ++i) {
713 if (mailfrom[i] == '<') lp = i;
714 if (mailfrom[i] == '>') rp = i;
716 if ((lp>=0)&&(rp>lp)) {
718 strcpy(mailfrom, &mailfrom[lp]);
723 } while (scan_done == 0);
724 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
727 /* Figure out what mail exchanger host we have to connect to */
728 num_mxhosts = getmx(mxhosts, node);
729 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
730 if (num_mxhosts < 1) {
732 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
736 for (mx=0; mx<num_mxhosts; ++mx) {
737 extract(buf, mxhosts, mx);
738 lprintf(9, "Trying <%s>\n", buf);
739 sock = sock_connect(buf, "25", "tcp");
740 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
741 if (sock >= 0) lprintf(9, "Connected!\n");
742 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
743 if (sock >= 0) break;
747 *status = 4; /* dsn is already filled in */
751 /* Process the SMTP greeting from the server */
752 if (ml_sock_gets(sock, buf) < 0) {
754 strcpy(dsn, "Connection broken during SMTP conversation");
757 lprintf(9, "<%s\n", buf);
761 safestrncpy(dsn, &buf[4], 1023);
766 safestrncpy(dsn, &buf[4], 1023);
771 /* At this point we know we are talking to a real SMTP server */
773 /* Do a HELO command */
774 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
775 lprintf(9, ">%s", buf);
776 sock_write(sock, buf, strlen(buf));
777 if (ml_sock_gets(sock, buf) < 0) {
779 strcpy(dsn, "Connection broken during SMTP HELO");
782 lprintf(9, "<%s\n", buf);
786 safestrncpy(dsn, &buf[4], 1023);
791 safestrncpy(dsn, &buf[4], 1023);
797 /* HELO succeeded, now try the MAIL From: command */
798 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
799 lprintf(9, ">%s", buf);
800 sock_write(sock, buf, strlen(buf));
801 if (ml_sock_gets(sock, buf) < 0) {
803 strcpy(dsn, "Connection broken during SMTP MAIL");
806 lprintf(9, "<%s\n", buf);
810 safestrncpy(dsn, &buf[4], 1023);
815 safestrncpy(dsn, &buf[4], 1023);
821 /* MAIL succeeded, now try the RCPT To: command */
822 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
823 lprintf(9, ">%s", buf);
824 sock_write(sock, buf, strlen(buf));
825 if (ml_sock_gets(sock, buf) < 0) {
827 strcpy(dsn, "Connection broken during SMTP RCPT");
830 lprintf(9, "<%s\n", buf);
834 safestrncpy(dsn, &buf[4], 1023);
839 safestrncpy(dsn, &buf[4], 1023);
845 /* RCPT succeeded, now try the DATA command */
846 lprintf(9, ">DATA\n");
847 sock_write(sock, "DATA\r\n", 6);
848 if (ml_sock_gets(sock, buf) < 0) {
850 strcpy(dsn, "Connection broken during SMTP DATA");
853 lprintf(9, "<%s\n", buf);
857 safestrncpy(dsn, &buf[4], 1023);
862 safestrncpy(dsn, &buf[4], 1023);
867 /* If we reach this point, the server is expecting data */
869 while (msg_size > 0) {
870 blocksize = sizeof(buf);
871 if (blocksize > msg_size) blocksize = msg_size;
872 fread(buf, blocksize, 1, msg_fp);
873 sock_write(sock, buf, blocksize);
874 msg_size -= blocksize;
876 if (buf[blocksize-1] != 10) {
877 lprintf(5, "Possible problem: message did not correctly "
878 "terminate. (expecting 0x10, got 0x%02x)\n",
882 sock_write(sock, ".\r\n", 3);
883 if (ml_sock_gets(sock, buf) < 0) {
885 strcpy(dsn, "Connection broken during SMTP message transmit");
888 lprintf(9, "%s\n", buf);
892 safestrncpy(dsn, &buf[4], 1023);
897 safestrncpy(dsn, &buf[4], 1023);
903 safestrncpy(dsn, &buf[4], 1023);
906 lprintf(9, ">QUIT\n");
907 sock_write(sock, "QUIT\r\n", 6);
908 ml_sock_gets(sock, buf);
909 lprintf(9, "<%s\n", buf);
911 bail: if (msg_fp != NULL) fclose(msg_fp);
919 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
920 * instructions for "5" codes (permanent fatal errors) and produce/deliver
921 * a "bounce" message (delivery status notification).
923 void smtp_do_bounce(char *instr) {
934 long bounce_msgid = (-1);
935 time_t submitted = 0L;
936 struct CtdlMessage *bmsg = NULL;
938 struct recptypes *valid;
939 int successful_bounce = 0;
941 lprintf(9, "smtp_do_bounce() called\n");
942 strcpy(bounceto, "");
944 lines = num_tokens(instr, '\n');
947 /* See if it's time to give up on delivery of this message */
948 for (i=0; i<lines; ++i) {
949 extract_token(buf, instr, i, '\n');
950 extract(key, buf, 0);
951 extract(addr, buf, 1);
952 if (!strcasecmp(key, "submitted")) {
953 submitted = atol(addr);
957 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
963 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
964 if (bmsg == NULL) return;
965 memset(bmsg, 0, sizeof(struct CtdlMessage));
967 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
968 bmsg->cm_anon_type = MES_NORMAL;
969 bmsg->cm_format_type = 1;
970 bmsg->cm_fields['A'] = strdoop("Citadel");
971 bmsg->cm_fields['O'] = strdoop(MAILROOM);
972 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
974 if (give_up) bmsg->cm_fields['M'] = strdoop(
975 "A message you sent could not be delivered to some or all of its recipients\n"
976 "due to prolonged unavailability of its destination(s).\n"
977 "Giving up on the following addresses:\n\n"
980 else bmsg->cm_fields['M'] = strdoop(
981 "A message you sent could not be delivered to some or all of its recipients.\n"
982 "The following addresses were undeliverable:\n\n"
986 * Now go through the instructions checking for stuff.
989 for (i=0; i<lines; ++i) {
990 extract_token(buf, instr, i, '\n');
991 extract(key, buf, 0);
992 extract(addr, buf, 1);
993 status = extract_int(buf, 2);
994 extract(dsn, buf, 3);
997 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
998 key, addr, status, dsn);
1000 if (!strcasecmp(key, "bounceto")) {
1001 strcpy(bounceto, addr);
1005 (!strcasecmp(key, "local"))
1006 || (!strcasecmp(key, "remote"))
1007 || (!strcasecmp(key, "ignet"))
1008 || (!strcasecmp(key, "room"))
1010 if (status == 5) bounce_this = 1;
1011 if (give_up) bounce_this = 1;
1017 if (bmsg->cm_fields['M'] == NULL) {
1018 lprintf(2, "ERROR ... M field is null "
1019 "(%s:%d)\n", __FILE__, __LINE__);
1022 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1023 strlen(bmsg->cm_fields['M']) + 1024 );
1024 strcat(bmsg->cm_fields['M'], addr);
1025 strcat(bmsg->cm_fields['M'], ": ");
1026 strcat(bmsg->cm_fields['M'], dsn);
1027 strcat(bmsg->cm_fields['M'], "\n");
1029 remove_token(instr, i, '\n');
1035 /* Deliver the bounce if there's anything worth mentioning */
1036 lprintf(9, "num_bounces = %d\n", num_bounces);
1037 if (num_bounces > 0) {
1039 /* First try the user who sent the message */
1040 lprintf(9, "bounce to user? <%s>\n", bounceto);
1042 if (strlen(bounceto) == 0) {
1043 lprintf(7, "No bounce address specified\n");
1044 bounce_msgid = (-1L);
1047 /* Can we deliver the bounce to the original sender? */
1048 valid = validate_recipients(bounceto);
1049 if (valid != NULL) {
1050 if (valid->num_error == 0) {
1051 CtdlSubmitMsg(bmsg, valid, "");
1052 successful_bounce = 1;
1056 /* If not, post it in the Aide> room */
1057 if (successful_bounce == 0) {
1058 CtdlSubmitMsg(bmsg, NULL, AIDEROOM);
1062 CtdlFreeMessage(bmsg);
1063 lprintf(9, "Done processing bounces\n");
1068 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1069 * set of delivery instructions for completed deliveries and remove them.
1071 * It returns the number of incomplete deliveries remaining.
1073 int smtp_purge_completed_deliveries(char *instr) {
1084 lines = num_tokens(instr, '\n');
1085 for (i=0; i<lines; ++i) {
1086 extract_token(buf, instr, i, '\n');
1087 extract(key, buf, 0);
1088 extract(addr, buf, 1);
1089 status = extract_int(buf, 2);
1090 extract(dsn, buf, 3);
1095 (!strcasecmp(key, "local"))
1096 || (!strcasecmp(key, "remote"))
1097 || (!strcasecmp(key, "ignet"))
1098 || (!strcasecmp(key, "room"))
1100 if (status == 2) completed = 1;
1105 remove_token(instr, i, '\n');
1118 * Called by smtp_do_queue() to handle an individual message.
1120 void smtp_do_procmsg(long msgnum, void *userdata) {
1121 struct CtdlMessage *msg;
1123 char *results = NULL;
1131 long text_msgid = (-1);
1132 int incomplete_deliveries_remaining;
1133 time_t attempted = 0L;
1134 time_t last_attempted = 0L;
1135 time_t retry = SMTP_RETRY_INTERVAL;
1137 lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1139 msg = CtdlFetchMessage(msgnum);
1141 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1145 instr = strdoop(msg->cm_fields['M']);
1146 CtdlFreeMessage(msg);
1148 /* Strip out the headers amd any other non-instruction line */
1149 lines = num_tokens(instr, '\n');
1150 for (i=0; i<lines; ++i) {
1151 extract_token(buf, instr, i, '\n');
1152 if (num_tokens(buf, '|') < 2) {
1153 remove_token(instr, i, '\n');
1159 /* Learn the message ID and find out about recent delivery attempts */
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 if (!strcasecmp(key, "msgid")) {
1165 text_msgid = extract_long(buf, 1);
1167 if (!strcasecmp(key, "retry")) {
1168 /* double the retry interval after each attempt */
1169 retry = extract_long(buf, 1) * 2L;
1170 if (retry > SMTP_RETRY_MAX) {
1171 retry = SMTP_RETRY_MAX;
1173 remove_token(instr, i, '\n');
1175 if (!strcasecmp(key, "attempted")) {
1176 attempted = extract_long(buf, 1);
1177 if (attempted > last_attempted)
1178 last_attempted = attempted;
1183 * Postpone delivery if we've already tried recently.
1185 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1186 lprintf(7, "Retry time not yet reached.\n");
1193 * Bail out if there's no actual message associated with this
1195 if (text_msgid < 0L) {
1196 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1201 /* Plow through the instructions looking for 'remote' directives and
1202 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1203 * were experienced and it's time to try again)
1205 lines = num_tokens(instr, '\n');
1206 for (i=0; i<lines; ++i) {
1207 extract_token(buf, instr, i, '\n');
1208 extract(key, buf, 0);
1209 extract(addr, buf, 1);
1210 status = extract_int(buf, 2);
1211 extract(dsn, buf, 3);
1212 if ( (!strcasecmp(key, "remote"))
1213 && ((status==0)||(status==3)||(status==4)) ) {
1214 remove_token(instr, i, '\n');
1217 lprintf(9, "SMTP: Trying <%s>\n", addr);
1218 smtp_try(key, addr, &status, dsn, text_msgid);
1220 if (results == NULL) {
1221 results = mallok(1024);
1222 memset(results, 0, 1024);
1225 results = reallok(results,
1226 strlen(results) + 1024);
1228 sprintf(&results[strlen(results)],
1230 key, addr, status, dsn);
1235 if (results != NULL) {
1236 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1237 strcat(instr, results);
1242 /* Generate 'bounce' messages */
1243 smtp_do_bounce(instr);
1245 /* Go through the delivery list, deleting completed deliveries */
1246 incomplete_deliveries_remaining =
1247 smtp_purge_completed_deliveries(instr);
1251 * No delivery instructions remain, so delete both the instructions
1252 * message and the message message.
1254 if (incomplete_deliveries_remaining <= 0) {
1255 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1256 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1261 * Uncompleted delivery instructions remain, so delete the old
1262 * instructions and replace with the updated ones.
1264 if (incomplete_deliveries_remaining > 0) {
1265 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1266 msg = mallok(sizeof(struct CtdlMessage));
1267 memset(msg, 0, sizeof(struct CtdlMessage));
1268 msg->cm_magic = CTDLMESSAGE_MAGIC;
1269 msg->cm_anon_type = MES_NORMAL;
1270 msg->cm_format_type = FMT_RFC822;
1271 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1272 snprintf(msg->cm_fields['M'],
1274 "Content-type: %s\n\n%s\n"
1277 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1279 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1280 CtdlFreeMessage(msg);
1290 * Run through the queue sending out messages.
1292 void smtp_do_queue(void) {
1293 static int doing_queue = 0;
1296 * This is a simple concurrency check to make sure only one queue run
1297 * is done at a time. We could do this with a mutex, but since we
1298 * don't really require extremely fine granularity here, we'll do it
1299 * with a static variable instead.
1301 if (doing_queue) return;
1305 * Go ahead and run the queue
1307 lprintf(7, "SMTP: processing outbound queue\n");
1309 if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1310 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1313 CtdlForEachMessage(MSGS_ALL, 0L, (-127),
1314 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1316 lprintf(7, "SMTP: queue run completed\n");
1323 /*****************************************************************************/
1324 /* SMTP UTILITY COMMANDS */
1325 /*****************************************************************************/
1327 void cmd_smtp(char *argbuf) {
1334 if (CtdlAccessCheck(ac_aide)) return;
1336 extract(cmd, argbuf, 0);
1338 if (!strcasecmp(cmd, "mx")) {
1339 extract(node, argbuf, 1);
1340 num_mxhosts = getmx(buf, node);
1341 cprintf("%d %d MX hosts listed for %s\n",
1342 LISTING_FOLLOWS, num_mxhosts, node);
1343 for (i=0; i<num_mxhosts; ++i) {
1344 extract(node, buf, i);
1345 cprintf("%s\n", node);
1351 else if (!strcasecmp(cmd, "runqueue")) {
1353 cprintf("%d All outbound SMTP will be retried now.\n", OK);
1358 cprintf("%d Invalid command.\n", ERROR+ILLEGAL_VALUE);
1366 /*****************************************************************************/
1367 /* MODULE INITIALIZATION STUFF */
1368 /*****************************************************************************/
1371 char *Dynamic_Module_Init(void)
1373 SYM_SMTP = CtdlGetDynamicSymbol();
1375 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1380 CtdlRegisterServiceHook(0, /* ...and locally */
1385 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1);
1386 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1387 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");