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;
434 case rfc822_room_delivery:
435 cprintf("250 Delivering to room '%s'\r\n", user);
436 cprintf("250 %s is a valid recipient.\r\n", user);
437 if (SMTP->valid.num_room > 0) {
438 strcat(SMTP->valid.recp_room, "|");
440 strcat(SMTP->valid.recp_room, user);
441 SMTP->valid.num_room += 1;
444 case rfc822_address_on_citadel_network:
445 cprintf("250 Delivering to room '%s'\r\n", user);
449 case rfc822_no_such_user:
450 cprintf("550 %s: no such user\r\n", recp);
453 case rfc822_address_nonlocal:
454 if (SMTP->message_originated_locally == 0) {
455 cprintf("551 Relaying denied.\r\n");
458 cprintf("250 Remote recipient %s ok\r\n", recp);
466 cprintf("599 Unknown error\r\n");
473 * Implements the DATA command
475 void smtp_data(void) {
477 struct CtdlMessage *msg;
481 if (strlen(SMTP->from) == 0) {
482 cprintf("503 Need MAIL command first.\r\n");
486 if (SMTP->number_of_recipients < 1) {
487 cprintf("503 Need RCPT command first.\r\n");
491 cprintf("354 Transmit message now; terminate with '.' by itself\r\n");
493 datestring(nowstamp, time(NULL), DATESTRING_RFC822);
496 if (body != NULL) snprintf(body, 4096,
497 "Received: from %s\n"
504 body = CtdlReadMessageBody(".", config.c_maxmsglen, body);
506 cprintf("550 Unable to save message: internal error.\r\n");
510 lprintf(9, "Converting message...\n");
511 msg = convert_internet_message(body);
513 /* If the user is locally authenticated, FORCE the From: header to
514 * show up as the real sender. Yes, this violates the RFC standard,
515 * but IT MAKES SENSE. Comment it out if you don't like this behavior.
518 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
519 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
520 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
521 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
522 msg->cm_fields['N'] = strdoop(config.c_nodename);
523 msg->cm_fields['H'] = strdoop(config.c_humannode);
526 /* Submit the message into the Citadel system. */
527 msgnum = CtdlSubmitMsg(msg, &SMTP->valid, "");
528 CtdlFreeMessage(msg);
531 cprintf("250 Message accepted.\r\n");
534 cprintf("550 Internal delivery error\r\n");
537 smtp_data_clear(); /* clear out the buffers now */
544 * Main command loop for SMTP sessions.
546 void smtp_command_loop(void) {
550 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
551 if (client_gets(cmdbuf) < 1) {
552 lprintf(3, "SMTP socket is broken. Ending session.\n");
556 lprintf(5, "citserver[%3d]: %s\n", CC->cs_pid, cmdbuf);
557 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
559 if (SMTP->command_state == smtp_user) {
560 smtp_get_user(cmdbuf);
563 else if (SMTP->command_state == smtp_password) {
564 smtp_get_pass(cmdbuf);
567 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
568 smtp_auth(&cmdbuf[5]);
571 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
575 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
576 smtp_hello(&cmdbuf[5], 1);
579 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
580 smtp_expn(&cmdbuf[5]);
583 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
584 smtp_hello(&cmdbuf[5], 0);
587 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
591 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
592 smtp_mail(&cmdbuf[5]);
595 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
596 cprintf("250 NOOP\r\n");
599 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
600 cprintf("221 Goodbye...\r\n");
605 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
606 smtp_rcpt(&cmdbuf[5]);
609 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
613 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
614 smtp_vrfy(&cmdbuf[5]);
618 cprintf("502 I'm afraid I can't do that.\r\n");
626 /*****************************************************************************/
627 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
628 /*****************************************************************************/
635 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
638 void smtp_try(char *key, char *addr, int *status, char *dsn, long msgnum)
645 char user[SIZ], node[SIZ], name[SIZ];
651 size_t blocksize = 0;
654 /* Parse out the host portion of the recipient address */
655 process_rfc822_addr(addr, user, node, name);
657 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
660 /* Load the message out of the database into a temp file */
662 if (msg_fp == NULL) {
664 sprintf(dsn, "Error creating temporary file");
668 CtdlRedirectOutput(msg_fp, -1);
669 CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, 1);
670 CtdlRedirectOutput(NULL, -1);
671 fseek(msg_fp, 0L, SEEK_END);
672 msg_size = ftell(msg_fp);
676 /* Extract something to send later in the 'MAIL From:' command */
677 strcpy(mailfrom, "");
681 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
682 if (!strncasecmp(buf, "From:", 5)) {
683 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
685 for (i=0; i<strlen(mailfrom); ++i) {
686 if (!isprint(mailfrom[i])) {
687 strcpy(&mailfrom[i], &mailfrom[i+1]);
692 /* Strip out parenthesized names */
695 for (i=0; i<strlen(mailfrom); ++i) {
696 if (mailfrom[i] == '(') lp = i;
697 if (mailfrom[i] == ')') rp = i;
699 if ((lp>0)&&(rp>lp)) {
700 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
703 /* Prefer brokketized names */
706 for (i=0; i<strlen(mailfrom); ++i) {
707 if (mailfrom[i] == '<') lp = i;
708 if (mailfrom[i] == '>') rp = i;
710 if ((lp>=0)&&(rp>lp)) {
712 strcpy(mailfrom, &mailfrom[lp]);
717 } while (scan_done == 0);
718 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
721 /* Figure out what mail exchanger host we have to connect to */
722 num_mxhosts = getmx(mxhosts, node);
723 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
724 if (num_mxhosts < 1) {
726 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
730 for (mx=0; mx<num_mxhosts; ++mx) {
731 extract(buf, mxhosts, mx);
732 lprintf(9, "Trying <%s>\n", buf);
733 sock = sock_connect(buf, "25", "tcp");
734 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
735 if (sock >= 0) lprintf(9, "Connected!\n");
736 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
737 if (sock >= 0) break;
741 *status = 4; /* dsn is already filled in */
745 /* Process the SMTP greeting from the server */
746 if (ml_sock_gets(sock, buf) < 0) {
748 strcpy(dsn, "Connection broken during SMTP conversation");
751 lprintf(9, "<%s\n", buf);
755 safestrncpy(dsn, &buf[4], 1023);
760 safestrncpy(dsn, &buf[4], 1023);
765 /* At this point we know we are talking to a real SMTP server */
767 /* Do a HELO command */
768 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
769 lprintf(9, ">%s", buf);
770 sock_write(sock, buf, strlen(buf));
771 if (ml_sock_gets(sock, buf) < 0) {
773 strcpy(dsn, "Connection broken during SMTP HELO");
776 lprintf(9, "<%s\n", buf);
780 safestrncpy(dsn, &buf[4], 1023);
785 safestrncpy(dsn, &buf[4], 1023);
791 /* HELO succeeded, now try the MAIL From: command */
792 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
793 lprintf(9, ">%s", buf);
794 sock_write(sock, buf, strlen(buf));
795 if (ml_sock_gets(sock, buf) < 0) {
797 strcpy(dsn, "Connection broken during SMTP MAIL");
800 lprintf(9, "<%s\n", buf);
804 safestrncpy(dsn, &buf[4], 1023);
809 safestrncpy(dsn, &buf[4], 1023);
815 /* MAIL succeeded, now try the RCPT To: command */
816 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
817 lprintf(9, ">%s", buf);
818 sock_write(sock, buf, strlen(buf));
819 if (ml_sock_gets(sock, buf) < 0) {
821 strcpy(dsn, "Connection broken during SMTP RCPT");
824 lprintf(9, "<%s\n", buf);
828 safestrncpy(dsn, &buf[4], 1023);
833 safestrncpy(dsn, &buf[4], 1023);
839 /* RCPT succeeded, now try the DATA command */
840 lprintf(9, ">DATA\n");
841 sock_write(sock, "DATA\r\n", 6);
842 if (ml_sock_gets(sock, buf) < 0) {
844 strcpy(dsn, "Connection broken during SMTP DATA");
847 lprintf(9, "<%s\n", buf);
851 safestrncpy(dsn, &buf[4], 1023);
856 safestrncpy(dsn, &buf[4], 1023);
861 /* If we reach this point, the server is expecting data */
863 while (msg_size > 0) {
864 blocksize = sizeof(buf);
865 if (blocksize > msg_size) blocksize = msg_size;
866 fread(buf, blocksize, 1, msg_fp);
867 sock_write(sock, buf, blocksize);
868 msg_size -= blocksize;
870 if (buf[blocksize-1] != 10) {
871 lprintf(5, "Possible problem: message did not correctly "
872 "terminate. (expecting 0x10, got 0x%02x)\n",
876 sock_write(sock, ".\r\n", 3);
877 if (ml_sock_gets(sock, buf) < 0) {
879 strcpy(dsn, "Connection broken during SMTP message transmit");
882 lprintf(9, "%s\n", buf);
886 safestrncpy(dsn, &buf[4], 1023);
891 safestrncpy(dsn, &buf[4], 1023);
897 safestrncpy(dsn, &buf[4], 1023);
900 lprintf(9, ">QUIT\n");
901 sock_write(sock, "QUIT\r\n", 6);
902 ml_sock_gets(sock, buf);
903 lprintf(9, "<%s\n", buf);
905 bail: if (msg_fp != NULL) fclose(msg_fp);
913 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
914 * instructions for "5" codes (permanent fatal errors) and produce/deliver
915 * a "bounce" message (delivery status notification).
917 void smtp_do_bounce(char *instr) {
928 long bounce_msgid = (-1);
929 time_t submitted = 0L;
930 struct CtdlMessage *bmsg = NULL;
933 lprintf(9, "smtp_do_bounce() called\n");
934 strcpy(bounceto, "");
936 lines = num_tokens(instr, '\n');
939 /* See if it's time to give up on delivery of this message */
940 for (i=0; i<lines; ++i) {
941 extract_token(buf, instr, i, '\n');
942 extract(key, buf, 0);
943 extract(addr, buf, 1);
944 if (!strcasecmp(key, "submitted")) {
945 submitted = atol(addr);
949 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
955 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
956 if (bmsg == NULL) return;
957 memset(bmsg, 0, sizeof(struct CtdlMessage));
959 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
960 bmsg->cm_anon_type = MES_NORMAL;
961 bmsg->cm_format_type = 1;
962 bmsg->cm_fields['A'] = strdoop("Citadel");
963 bmsg->cm_fields['O'] = strdoop(MAILROOM);
964 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
966 if (give_up) bmsg->cm_fields['M'] = strdoop(
967 "A message you sent could not be delivered to some or all of its recipients\n"
968 "due to prolonged unavailability of its destination(s).\n"
969 "Giving up on the following addresses:\n\n"
972 else bmsg->cm_fields['M'] = strdoop(
973 "A message you sent could not be delivered to some or all of its recipients.\n"
974 "The following addresses were undeliverable:\n\n"
978 * Now go through the instructions checking for stuff.
981 for (i=0; i<lines; ++i) {
982 extract_token(buf, instr, i, '\n');
983 extract(key, buf, 0);
984 extract(addr, buf, 1);
985 status = extract_int(buf, 2);
986 extract(dsn, buf, 3);
989 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
990 key, addr, status, dsn);
992 if (!strcasecmp(key, "bounceto")) {
993 strcpy(bounceto, addr);
997 (!strcasecmp(key, "local"))
998 || (!strcasecmp(key, "remote"))
999 || (!strcasecmp(key, "ignet"))
1000 || (!strcasecmp(key, "room"))
1002 if (status == 5) bounce_this = 1;
1003 if (give_up) bounce_this = 1;
1009 if (bmsg->cm_fields['M'] == NULL) {
1010 lprintf(2, "ERROR ... M field is null "
1011 "(%s:%d)\n", __FILE__, __LINE__);
1014 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1015 strlen(bmsg->cm_fields['M']) + 1024 );
1016 strcat(bmsg->cm_fields['M'], addr);
1017 strcat(bmsg->cm_fields['M'], ": ");
1018 strcat(bmsg->cm_fields['M'], dsn);
1019 strcat(bmsg->cm_fields['M'], "\n");
1021 remove_token(instr, i, '\n');
1027 /* Deliver the bounce if there's anything worth mentioning */
1028 lprintf(9, "num_bounces = %d\n", num_bounces);
1029 if (num_bounces > 0) {
1031 /* First try the user who sent the message */
1032 lprintf(9, "bounce to user? <%s>\n", bounceto);
1034 if (strlen(bounceto) == 0) {
1035 lprintf(7, "No bounce address specified\n");
1036 bounce_msgid = (-1L);
1038 /* FIXME this won't work
1039 else if (mes_type = alias(bounceto), mes_type == MES_ERROR) {
1040 lprintf(7, "Invalid bounce address <%s>\n", bounceto);
1041 bounce_msgid = (-1L);
1044 bounce_msgid = CtdlSubmitMsg(bmsg,
1051 /* Otherwise, go to the Aide> room */
1052 lprintf(9, "bounce to room?\n");
1053 if (bounce_msgid < 0L) bounce_msgid = CtdlSubmitMsg(bmsg,
1057 CtdlFreeMessage(bmsg);
1058 lprintf(9, "Done processing bounces\n");
1063 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1064 * set of delivery instructions for completed deliveries and remove them.
1066 * It returns the number of incomplete deliveries remaining.
1068 int smtp_purge_completed_deliveries(char *instr) {
1079 lines = num_tokens(instr, '\n');
1080 for (i=0; i<lines; ++i) {
1081 extract_token(buf, instr, i, '\n');
1082 extract(key, buf, 0);
1083 extract(addr, buf, 1);
1084 status = extract_int(buf, 2);
1085 extract(dsn, buf, 3);
1090 (!strcasecmp(key, "local"))
1091 || (!strcasecmp(key, "remote"))
1092 || (!strcasecmp(key, "ignet"))
1093 || (!strcasecmp(key, "room"))
1095 if (status == 2) completed = 1;
1100 remove_token(instr, i, '\n');
1113 * Called by smtp_do_queue() to handle an individual message.
1115 void smtp_do_procmsg(long msgnum, void *userdata) {
1116 struct CtdlMessage *msg;
1118 char *results = NULL;
1126 long text_msgid = (-1);
1127 int incomplete_deliveries_remaining;
1128 time_t attempted = 0L;
1129 time_t last_attempted = 0L;
1130 time_t retry = SMTP_RETRY_INTERVAL;
1132 lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1134 msg = CtdlFetchMessage(msgnum);
1136 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1140 instr = strdoop(msg->cm_fields['M']);
1141 CtdlFreeMessage(msg);
1143 /* Strip out the headers amd any other non-instruction line */
1144 lines = num_tokens(instr, '\n');
1145 for (i=0; i<lines; ++i) {
1146 extract_token(buf, instr, i, '\n');
1147 if (num_tokens(buf, '|') < 2) {
1148 remove_token(instr, i, '\n');
1154 /* Learn the message ID and find out about recent delivery attempts */
1155 lines = num_tokens(instr, '\n');
1156 for (i=0; i<lines; ++i) {
1157 extract_token(buf, instr, i, '\n');
1158 extract(key, buf, 0);
1159 if (!strcasecmp(key, "msgid")) {
1160 text_msgid = extract_long(buf, 1);
1162 if (!strcasecmp(key, "retry")) {
1163 /* double the retry interval after each attempt */
1164 retry = extract_long(buf, 1) * 2L;
1165 if (retry > SMTP_RETRY_MAX) {
1166 retry = SMTP_RETRY_MAX;
1168 remove_token(instr, i, '\n');
1170 if (!strcasecmp(key, "attempted")) {
1171 attempted = extract_long(buf, 1);
1172 if (attempted > last_attempted)
1173 last_attempted = attempted;
1178 * Postpone delivery if we've already tried recently.
1180 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1181 lprintf(7, "Retry time not yet reached.\n");
1188 * Bail out if there's no actual message associated with this
1190 if (text_msgid < 0L) {
1191 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1196 /* Plow through the instructions looking for 'remote' directives and
1197 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1198 * were experienced and it's time to try again)
1200 lines = num_tokens(instr, '\n');
1201 for (i=0; i<lines; ++i) {
1202 extract_token(buf, instr, i, '\n');
1203 extract(key, buf, 0);
1204 extract(addr, buf, 1);
1205 status = extract_int(buf, 2);
1206 extract(dsn, buf, 3);
1207 if ( (!strcasecmp(key, "remote"))
1208 && ((status==0)||(status==3)||(status==4)) ) {
1209 remove_token(instr, i, '\n');
1212 lprintf(9, "SMTP: Trying <%s>\n", addr);
1213 smtp_try(key, addr, &status, dsn, text_msgid);
1215 if (results == NULL) {
1216 results = mallok(1024);
1217 memset(results, 0, 1024);
1220 results = reallok(results,
1221 strlen(results) + 1024);
1223 sprintf(&results[strlen(results)],
1225 key, addr, status, dsn);
1230 if (results != NULL) {
1231 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1232 strcat(instr, results);
1237 /* Generate 'bounce' messages */
1238 smtp_do_bounce(instr);
1240 /* Go through the delivery list, deleting completed deliveries */
1241 incomplete_deliveries_remaining =
1242 smtp_purge_completed_deliveries(instr);
1246 * No delivery instructions remain, so delete both the instructions
1247 * message and the message message.
1249 if (incomplete_deliveries_remaining <= 0) {
1250 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1251 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1256 * Uncompleted delivery instructions remain, so delete the old
1257 * instructions and replace with the updated ones.
1259 if (incomplete_deliveries_remaining > 0) {
1260 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1261 msg = mallok(sizeof(struct CtdlMessage));
1262 memset(msg, 0, sizeof(struct CtdlMessage));
1263 msg->cm_magic = CTDLMESSAGE_MAGIC;
1264 msg->cm_anon_type = MES_NORMAL;
1265 msg->cm_format_type = FMT_RFC822;
1266 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1267 snprintf(msg->cm_fields['M'],
1269 "Content-type: %s\n\n%s\n"
1272 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1274 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1275 CtdlFreeMessage(msg);
1285 * Run through the queue sending out messages.
1287 void smtp_do_queue(void) {
1288 static int doing_queue = 0;
1291 * This is a simple concurrency check to make sure only one queue run
1292 * is done at a time. We could do this with a mutex, but since we
1293 * don't really require extremely fine granularity here, we'll do it
1294 * with a static variable instead.
1296 if (doing_queue) return;
1300 * Go ahead and run the queue
1302 lprintf(7, "SMTP: processing outbound queue\n");
1304 if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1305 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1308 CtdlForEachMessage(MSGS_ALL, 0L, (-127),
1309 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1311 lprintf(7, "SMTP: queue run completed\n");
1318 /*****************************************************************************/
1319 /* SMTP UTILITY COMMANDS */
1320 /*****************************************************************************/
1322 void cmd_smtp(char *argbuf) {
1329 if (CtdlAccessCheck(ac_aide)) return;
1331 extract(cmd, argbuf, 0);
1333 if (!strcasecmp(cmd, "mx")) {
1334 extract(node, argbuf, 1);
1335 num_mxhosts = getmx(buf, node);
1336 cprintf("%d %d MX hosts listed for %s\n",
1337 LISTING_FOLLOWS, num_mxhosts, node);
1338 for (i=0; i<num_mxhosts; ++i) {
1339 extract(node, buf, i);
1340 cprintf("%s\n", node);
1346 else if (!strcasecmp(cmd, "runqueue")) {
1348 cprintf("%d All outbound SMTP will be retried now.\n", OK);
1353 cprintf("%d Invalid command.\n", ERROR+ILLEGAL_VALUE);
1361 /*****************************************************************************/
1362 /* MODULE INITIALIZATION STUFF */
1363 /*****************************************************************************/
1366 char *Dynamic_Module_Init(void)
1368 SYM_SMTP = CtdlGetDynamicSymbol();
1370 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1375 CtdlRegisterServiceHook(0, /* ...and locally */
1380 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1);
1381 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1382 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");