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");
726 /* Figure out what mail exchanger host we have to connect to */
727 num_mxhosts = getmx(mxhosts, node);
728 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
729 if (num_mxhosts < 1) {
731 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
735 for (mx=0; mx<num_mxhosts; ++mx) {
736 extract(buf, mxhosts, mx);
737 lprintf(9, "Trying <%s>\n", buf);
738 sock = sock_connect(buf, "25", "tcp");
739 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
740 if (sock >= 0) lprintf(9, "Connected!\n");
741 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
742 if (sock >= 0) break;
746 *status = 4; /* dsn is already filled in */
750 /* Process the SMTP greeting from the server */
751 if (ml_sock_gets(sock, buf) < 0) {
753 strcpy(dsn, "Connection broken during SMTP conversation");
756 lprintf(9, "<%s\n", buf);
760 safestrncpy(dsn, &buf[4], 1023);
765 safestrncpy(dsn, &buf[4], 1023);
770 /* At this point we know we are talking to a real SMTP server */
772 /* Do a HELO command */
773 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
774 lprintf(9, ">%s", buf);
775 sock_write(sock, buf, strlen(buf));
776 if (ml_sock_gets(sock, buf) < 0) {
778 strcpy(dsn, "Connection broken during SMTP HELO");
781 lprintf(9, "<%s\n", buf);
785 safestrncpy(dsn, &buf[4], 1023);
790 safestrncpy(dsn, &buf[4], 1023);
796 /* HELO succeeded, now try the MAIL From: command */
797 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
798 lprintf(9, ">%s", buf);
799 sock_write(sock, buf, strlen(buf));
800 if (ml_sock_gets(sock, buf) < 0) {
802 strcpy(dsn, "Connection broken during SMTP MAIL");
805 lprintf(9, "<%s\n", buf);
809 safestrncpy(dsn, &buf[4], 1023);
814 safestrncpy(dsn, &buf[4], 1023);
820 /* MAIL succeeded, now try the RCPT To: command */
821 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
822 lprintf(9, ">%s", buf);
823 sock_write(sock, buf, strlen(buf));
824 if (ml_sock_gets(sock, buf) < 0) {
826 strcpy(dsn, "Connection broken during SMTP RCPT");
829 lprintf(9, "<%s\n", buf);
833 safestrncpy(dsn, &buf[4], 1023);
838 safestrncpy(dsn, &buf[4], 1023);
844 /* RCPT succeeded, now try the DATA command */
845 lprintf(9, ">DATA\n");
846 sock_write(sock, "DATA\r\n", 6);
847 if (ml_sock_gets(sock, buf) < 0) {
849 strcpy(dsn, "Connection broken during SMTP DATA");
852 lprintf(9, "<%s\n", buf);
856 safestrncpy(dsn, &buf[4], 1023);
861 safestrncpy(dsn, &buf[4], 1023);
866 /* If we reach this point, the server is expecting data */
868 while (msg_size > 0) {
869 blocksize = sizeof(buf);
870 if (blocksize > msg_size) blocksize = msg_size;
871 fread(buf, blocksize, 1, msg_fp);
872 sock_write(sock, buf, blocksize);
873 msg_size -= blocksize;
875 if (buf[blocksize-1] != 10) {
876 lprintf(5, "Possible problem: message did not correctly "
877 "terminate. (expecting 0x10, got 0x%02x)\n",
881 sock_write(sock, ".\r\n", 3);
882 if (ml_sock_gets(sock, buf) < 0) {
884 strcpy(dsn, "Connection broken during SMTP message transmit");
887 lprintf(9, "%s\n", buf);
891 safestrncpy(dsn, &buf[4], 1023);
896 safestrncpy(dsn, &buf[4], 1023);
902 safestrncpy(dsn, &buf[4], 1023);
905 lprintf(9, ">QUIT\n");
906 sock_write(sock, "QUIT\r\n", 6);
907 ml_sock_gets(sock, buf);
908 lprintf(9, "<%s\n", buf);
910 bail: if (msg_fp != NULL) fclose(msg_fp);
918 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
919 * instructions for "5" codes (permanent fatal errors) and produce/deliver
920 * a "bounce" message (delivery status notification).
922 void smtp_do_bounce(char *instr) {
933 long bounce_msgid = (-1);
934 time_t submitted = 0L;
935 struct CtdlMessage *bmsg = NULL;
937 struct recptypes *valid;
938 int successful_bounce = 0;
940 lprintf(9, "smtp_do_bounce() called\n");
941 strcpy(bounceto, "");
943 lines = num_tokens(instr, '\n');
946 /* See if it's time to give up on delivery of this message */
947 for (i=0; i<lines; ++i) {
948 extract_token(buf, instr, i, '\n');
949 extract(key, buf, 0);
950 extract(addr, buf, 1);
951 if (!strcasecmp(key, "submitted")) {
952 submitted = atol(addr);
956 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
962 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
963 if (bmsg == NULL) return;
964 memset(bmsg, 0, sizeof(struct CtdlMessage));
966 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
967 bmsg->cm_anon_type = MES_NORMAL;
968 bmsg->cm_format_type = 1;
969 bmsg->cm_fields['A'] = strdoop("Citadel");
970 bmsg->cm_fields['O'] = strdoop(MAILROOM);
971 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
973 if (give_up) bmsg->cm_fields['M'] = strdoop(
974 "A message you sent could not be delivered to some or all of its recipients\n"
975 "due to prolonged unavailability of its destination(s).\n"
976 "Giving up on the following addresses:\n\n"
979 else bmsg->cm_fields['M'] = strdoop(
980 "A message you sent could not be delivered to some or all of its recipients.\n"
981 "The following addresses were undeliverable:\n\n"
985 * Now go through the instructions checking for stuff.
988 for (i=0; i<lines; ++i) {
989 extract_token(buf, instr, i, '\n');
990 extract(key, buf, 0);
991 extract(addr, buf, 1);
992 status = extract_int(buf, 2);
993 extract(dsn, buf, 3);
996 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
997 key, addr, status, dsn);
999 if (!strcasecmp(key, "bounceto")) {
1000 strcpy(bounceto, addr);
1004 (!strcasecmp(key, "local"))
1005 || (!strcasecmp(key, "remote"))
1006 || (!strcasecmp(key, "ignet"))
1007 || (!strcasecmp(key, "room"))
1009 if (status == 5) bounce_this = 1;
1010 if (give_up) bounce_this = 1;
1016 if (bmsg->cm_fields['M'] == NULL) {
1017 lprintf(2, "ERROR ... M field is null "
1018 "(%s:%d)\n", __FILE__, __LINE__);
1021 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1022 strlen(bmsg->cm_fields['M']) + 1024 );
1023 strcat(bmsg->cm_fields['M'], addr);
1024 strcat(bmsg->cm_fields['M'], ": ");
1025 strcat(bmsg->cm_fields['M'], dsn);
1026 strcat(bmsg->cm_fields['M'], "\n");
1028 remove_token(instr, i, '\n');
1034 /* Deliver the bounce if there's anything worth mentioning */
1035 lprintf(9, "num_bounces = %d\n", num_bounces);
1036 if (num_bounces > 0) {
1038 /* First try the user who sent the message */
1039 lprintf(9, "bounce to user? <%s>\n", bounceto);
1041 if (strlen(bounceto) == 0) {
1042 lprintf(7, "No bounce address specified\n");
1043 bounce_msgid = (-1L);
1046 /* Can we deliver the bounce to the original sender? */
1047 valid = validate_recipients(bounceto);
1048 if (valid != NULL) {
1049 if (valid->num_error == 0) {
1050 CtdlSubmitMsg(bmsg, valid, "");
1051 successful_bounce = 1;
1055 /* If not, post it in the Aide> room */
1056 if (successful_bounce == 0) {
1057 CtdlSubmitMsg(bmsg, NULL, AIDEROOM);
1060 /* Free up the memory we used */
1061 if (valid != NULL) {
1066 CtdlFreeMessage(bmsg);
1067 lprintf(9, "Done processing bounces\n");
1072 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1073 * set of delivery instructions for completed deliveries and remove them.
1075 * It returns the number of incomplete deliveries remaining.
1077 int smtp_purge_completed_deliveries(char *instr) {
1088 lines = num_tokens(instr, '\n');
1089 for (i=0; i<lines; ++i) {
1090 extract_token(buf, instr, i, '\n');
1091 extract(key, buf, 0);
1092 extract(addr, buf, 1);
1093 status = extract_int(buf, 2);
1094 extract(dsn, buf, 3);
1099 (!strcasecmp(key, "local"))
1100 || (!strcasecmp(key, "remote"))
1101 || (!strcasecmp(key, "ignet"))
1102 || (!strcasecmp(key, "room"))
1104 if (status == 2) completed = 1;
1109 remove_token(instr, i, '\n');
1122 * Called by smtp_do_queue() to handle an individual message.
1124 void smtp_do_procmsg(long msgnum, void *userdata) {
1125 struct CtdlMessage *msg;
1127 char *results = NULL;
1135 long text_msgid = (-1);
1136 int incomplete_deliveries_remaining;
1137 time_t attempted = 0L;
1138 time_t last_attempted = 0L;
1139 time_t retry = SMTP_RETRY_INTERVAL;
1141 lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1143 msg = CtdlFetchMessage(msgnum);
1145 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1149 instr = strdoop(msg->cm_fields['M']);
1150 CtdlFreeMessage(msg);
1152 /* Strip out the headers amd any other non-instruction line */
1153 lines = num_tokens(instr, '\n');
1154 for (i=0; i<lines; ++i) {
1155 extract_token(buf, instr, i, '\n');
1156 if (num_tokens(buf, '|') < 2) {
1157 remove_token(instr, i, '\n');
1163 /* Learn the message ID and find out about recent delivery attempts */
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 if (!strcasecmp(key, "msgid")) {
1169 text_msgid = extract_long(buf, 1);
1171 if (!strcasecmp(key, "retry")) {
1172 /* double the retry interval after each attempt */
1173 retry = extract_long(buf, 1) * 2L;
1174 if (retry > SMTP_RETRY_MAX) {
1175 retry = SMTP_RETRY_MAX;
1177 remove_token(instr, i, '\n');
1179 if (!strcasecmp(key, "attempted")) {
1180 attempted = extract_long(buf, 1);
1181 if (attempted > last_attempted)
1182 last_attempted = attempted;
1187 * Postpone delivery if we've already tried recently.
1189 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1190 lprintf(7, "Retry time not yet reached.\n");
1197 * Bail out if there's no actual message associated with this
1199 if (text_msgid < 0L) {
1200 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1205 /* Plow through the instructions looking for 'remote' directives and
1206 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1207 * were experienced and it's time to try again)
1209 lines = num_tokens(instr, '\n');
1210 for (i=0; i<lines; ++i) {
1211 extract_token(buf, instr, i, '\n');
1212 extract(key, buf, 0);
1213 extract(addr, buf, 1);
1214 status = extract_int(buf, 2);
1215 extract(dsn, buf, 3);
1216 if ( (!strcasecmp(key, "remote"))
1217 && ((status==0)||(status==3)||(status==4)) ) {
1218 remove_token(instr, i, '\n');
1221 lprintf(9, "SMTP: Trying <%s>\n", addr);
1222 smtp_try(key, addr, &status, dsn, text_msgid);
1224 if (results == NULL) {
1225 results = mallok(1024);
1226 memset(results, 0, 1024);
1229 results = reallok(results,
1230 strlen(results) + 1024);
1232 sprintf(&results[strlen(results)],
1234 key, addr, status, dsn);
1239 if (results != NULL) {
1240 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1241 strcat(instr, results);
1246 /* Generate 'bounce' messages */
1247 smtp_do_bounce(instr);
1249 /* Go through the delivery list, deleting completed deliveries */
1250 incomplete_deliveries_remaining =
1251 smtp_purge_completed_deliveries(instr);
1255 * No delivery instructions remain, so delete both the instructions
1256 * message and the message message.
1258 if (incomplete_deliveries_remaining <= 0) {
1259 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1260 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1265 * Uncompleted delivery instructions remain, so delete the old
1266 * instructions and replace with the updated ones.
1268 if (incomplete_deliveries_remaining > 0) {
1269 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1270 msg = mallok(sizeof(struct CtdlMessage));
1271 memset(msg, 0, sizeof(struct CtdlMessage));
1272 msg->cm_magic = CTDLMESSAGE_MAGIC;
1273 msg->cm_anon_type = MES_NORMAL;
1274 msg->cm_format_type = FMT_RFC822;
1275 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1276 snprintf(msg->cm_fields['M'],
1278 "Content-type: %s\n\n%s\n"
1281 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1283 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1284 CtdlFreeMessage(msg);
1294 * Run through the queue sending out messages.
1296 void smtp_do_queue(void) {
1297 static int doing_queue = 0;
1300 * This is a simple concurrency check to make sure only one queue run
1301 * is done at a time. We could do this with a mutex, but since we
1302 * don't really require extremely fine granularity here, we'll do it
1303 * with a static variable instead.
1305 if (doing_queue) return;
1309 * Go ahead and run the queue
1311 lprintf(7, "SMTP: processing outbound queue\n");
1313 if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1314 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1317 CtdlForEachMessage(MSGS_ALL, 0L, (-127),
1318 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1320 lprintf(7, "SMTP: queue run completed\n");
1327 /*****************************************************************************/
1328 /* SMTP UTILITY COMMANDS */
1329 /*****************************************************************************/
1331 void cmd_smtp(char *argbuf) {
1338 if (CtdlAccessCheck(ac_aide)) return;
1340 extract(cmd, argbuf, 0);
1342 if (!strcasecmp(cmd, "mx")) {
1343 extract(node, argbuf, 1);
1344 num_mxhosts = getmx(buf, node);
1345 cprintf("%d %d MX hosts listed for %s\n",
1346 LISTING_FOLLOWS, num_mxhosts, node);
1347 for (i=0; i<num_mxhosts; ++i) {
1348 extract(node, buf, i);
1349 cprintf("%s\n", node);
1355 else if (!strcasecmp(cmd, "runqueue")) {
1357 cprintf("%d All outbound SMTP will be retried now.\n", OK);
1362 cprintf("%d Invalid command.\n", ERROR+ILLEGAL_VALUE);
1370 /*****************************************************************************/
1371 /* MODULE INITIALIZATION STUFF */
1372 /*****************************************************************************/
1375 char *Dynamic_Module_Init(void)
1377 SYM_SMTP = CtdlGetDynamicSymbol();
1379 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1384 CtdlRegisterServiceHook(0, /* ...and locally */
1389 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1);
1390 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1391 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");