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);
424 case rfc822_address_locally_validated:
425 SMTP->valid.num_local += 1;
428 case rfc822_address_on_citadel_network:
429 cprintf("250 %s is a valid recipient.\r\n", user);
430 CtdlReallocUserData(SYM_SMTP_RECPS,
431 strlen(SMTP_RECPS) + 1024 );
432 if (strlen(SMTP_RECPS) > 0) {
433 strcat(SMTP_RECPS, "|");
435 strcat(SMTP_RECPS, user);
438 case rfc822_room_delivery:
439 cprintf("250 Delivering to room '%s'\r\n", user);
440 CtdlReallocUserData(SYM_SMTP_ROOMS,
441 strlen(SMTP_ROOMS) + 1024 );
442 if (strlen(SMTP_ROOMS) > 0) {
443 strcat(SMTP_ROOMS, "|");
445 strcat(SMTP_RECPS, user);
448 case rfc822_no_such_user:
449 cprintf("550 %s: no such user\r\n", recp);
452 case rfc822_address_nonlocal:
453 if (SMTP->message_originated_locally == 0) {
454 cprintf("551 Relaying denied.\r\n");
457 cprintf("250 Remote recipient %s ok\r\n", recp);
458 CtdlReallocUserData(SYM_SMTP_RECPS,
459 strlen(SMTP_RECPS) + 1024 );
460 if (strlen(SMTP_RECPS) > 0) {
461 strcat(SMTP_RECPS, "|");
463 strcat(SMTP_RECPS, user);
469 cprintf("599 Unknown error\r\n");
475 * Back end for smtp_data() ... this does the actual delivery of the message
476 * Returns 0 on success, nonzero on failure
478 int smtp_message_delivery(struct CtdlMessage *msg) {
485 int successful_saves = 0; /* number of successful local saves */
486 int failed_saves = 0; /* number of failed deliveries */
487 int remote_spools = 0; /* number of copies to send out */
490 struct usersupp userbuf;
491 char *instr; /* Remote delivery instructions */
492 struct CtdlMessage *imsg;
494 lprintf(9, "smtp_message_delivery() called\n");
496 /* Fill in 'from' fields with envelope information if missing */
497 process_rfc822_addr(SMTP->from, user, node, name);
498 if (msg->cm_fields['A']==NULL) msg->cm_fields['A'] = strdoop(user);
499 if (msg->cm_fields['N']==NULL) msg->cm_fields['N'] = strdoop(node);
500 if (msg->cm_fields['H']==NULL) msg->cm_fields['H'] = strdoop(name);
501 if (msg->cm_fields['O']==NULL) msg->cm_fields['O'] = strdoop(MAILROOM);
503 /* Save the message in the queue */
504 msgid = CtdlSubmitMsg(msg,
509 valid = validate_recipients(char *recipients) ;
511 for (i=0; i<SMTP->number_of_recipients; ++i) {
512 extract_token(buf, SMTP_RECP, i, '\n');
513 extract(dtype, buf, 0);
515 /* Stuff local mailboxes */
516 if (!strcasecmp(dtype, "local")) {
517 extract(user, buf, 1);
518 if (getuser(&userbuf, user) == 0) {
519 MailboxName(room, &userbuf, MAILROOM);
520 CtdlSaveMsgPointerInRoom(room, msgid, 0);
528 /* Delivery to local non-mailbox rooms */
529 if (!strcasecmp(dtype, "room")) {
530 extract(room, buf, 1);
531 CtdlSaveMsgPointerInRoom(room, msgid, 0);
535 /* Delivery over the local Citadel network (IGnet) */
536 if (!strcasecmp(dtype, "ignet")) {
537 extract(user, buf, 1);
538 extract(node, buf, 2);
539 smtp_deliver_ignet(msg, user, node);
542 /* Remote delivery */
543 if (!strcasecmp(dtype, "remote")) {
544 extract(user, buf, 1);
545 instr = reallok(instr, strlen(instr) + 1024);
546 snprintf(&instr[strlen(instr)],
547 strlen(instr) + 1024,
555 /* If there are remote spools to be done, save the instructions */
556 if (remote_spools > 0) {
557 imsg = mallok(sizeof(struct CtdlMessage));
558 memset(imsg, 0, sizeof(struct CtdlMessage));
559 imsg->cm_magic = CTDLMESSAGE_MAGIC;
560 imsg->cm_anon_type = MES_NORMAL;
561 imsg->cm_format_type = FMT_RFC822;
562 imsg->cm_fields['M'] = instr;
563 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
564 CtdlFreeMessage(imsg);
567 /* If there are no remote spools, delete the message */
569 phree(instr); /* only needed here, because CtdlSubmitMsg()
570 * would free this buffer otherwise */
571 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgid, "");
574 return(failed_saves);
580 * Implements the DATA command
582 void smtp_data(void) {
584 struct CtdlMessage *msg;
588 if (strlen(SMTP->from) == 0) {
589 cprintf("503 Need MAIL command first.\r\n");
593 if (SMTP->number_of_recipients < 1) {
594 cprintf("503 Need RCPT command first.\r\n");
598 cprintf("354 Transmit message now; terminate with '.' by itself\r\n");
600 datestring(nowstamp, time(NULL), DATESTRING_RFC822);
603 if (body != NULL) snprintf(body, 4096,
604 "Received: from %s\n"
611 body = CtdlReadMessageBody(".", config.c_maxmsglen, body);
613 cprintf("550 Unable to save message: internal error.\r\n");
617 lprintf(9, "Converting message...\n");
618 msg = convert_internet_message(body);
620 /* If the user is locally authenticated, FORCE the From: header to
621 * show up as the real sender. Yes, this violates the RFC standard,
622 * but IT MAKES SENSE. Comment it out if you don't like this behavior.
625 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
626 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
627 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
628 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
629 msg->cm_fields['N'] = strdoop(config.c_nodename);
630 msg->cm_fields['H'] = strdoop(config.c_humannode);
633 /* Submit the message into the Citadel system. */
634 retval = smtp_message_delivery(msg);
635 CtdlFreeMessage(msg);
638 cprintf("250 Message accepted.\r\n");
641 cprintf("550 Internal delivery errors: %d\r\n", retval);
644 smtp_data_clear(); /* clear out the buffers now */
651 * Main command loop for SMTP sessions.
653 void smtp_command_loop(void) {
657 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
658 if (client_gets(cmdbuf) < 1) {
659 lprintf(3, "SMTP socket is broken. Ending session.\n");
663 lprintf(5, "citserver[%3d]: %s\n", CC->cs_pid, cmdbuf);
664 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
666 if (SMTP->command_state == smtp_user) {
667 smtp_get_user(cmdbuf);
670 else if (SMTP->command_state == smtp_password) {
671 smtp_get_pass(cmdbuf);
674 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
675 smtp_auth(&cmdbuf[5]);
678 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
682 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
683 smtp_hello(&cmdbuf[5], 1);
686 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
687 smtp_expn(&cmdbuf[5]);
690 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
691 smtp_hello(&cmdbuf[5], 0);
694 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
698 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
699 smtp_mail(&cmdbuf[5]);
702 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
703 cprintf("250 NOOP\r\n");
706 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
707 cprintf("221 Goodbye...\r\n");
712 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
713 smtp_rcpt(&cmdbuf[5]);
716 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
720 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
721 smtp_vrfy(&cmdbuf[5]);
725 cprintf("502 I'm afraid I can't do that.\r\n");
733 /*****************************************************************************/
734 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
735 /*****************************************************************************/
742 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
745 void smtp_try(char *key, char *addr, int *status, char *dsn, long msgnum)
752 char user[SIZ], node[SIZ], name[SIZ];
758 size_t blocksize = 0;
761 /* Parse out the host portion of the recipient address */
762 process_rfc822_addr(addr, user, node, name);
764 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
767 /* Load the message out of the database into a temp file */
769 if (msg_fp == NULL) {
771 sprintf(dsn, "Error creating temporary file");
775 CtdlRedirectOutput(msg_fp, -1);
776 CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, 1);
777 CtdlRedirectOutput(NULL, -1);
778 fseek(msg_fp, 0L, SEEK_END);
779 msg_size = ftell(msg_fp);
783 /* Extract something to send later in the 'MAIL From:' command */
784 strcpy(mailfrom, "");
788 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
789 if (!strncasecmp(buf, "From:", 5)) {
790 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
792 for (i=0; i<strlen(mailfrom); ++i) {
793 if (!isprint(mailfrom[i])) {
794 strcpy(&mailfrom[i], &mailfrom[i+1]);
799 /* Strip out parenthesized names */
802 for (i=0; i<strlen(mailfrom); ++i) {
803 if (mailfrom[i] == '(') lp = i;
804 if (mailfrom[i] == ')') rp = i;
806 if ((lp>0)&&(rp>lp)) {
807 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
810 /* Prefer brokketized names */
813 for (i=0; i<strlen(mailfrom); ++i) {
814 if (mailfrom[i] == '<') lp = i;
815 if (mailfrom[i] == '>') rp = i;
817 if ((lp>=0)&&(rp>lp)) {
819 strcpy(mailfrom, &mailfrom[lp]);
824 } while (scan_done == 0);
825 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
828 /* Figure out what mail exchanger host we have to connect to */
829 num_mxhosts = getmx(mxhosts, node);
830 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
831 if (num_mxhosts < 1) {
833 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
837 for (mx=0; mx<num_mxhosts; ++mx) {
838 extract(buf, mxhosts, mx);
839 lprintf(9, "Trying <%s>\n", buf);
840 sock = sock_connect(buf, "25", "tcp");
841 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
842 if (sock >= 0) lprintf(9, "Connected!\n");
843 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
844 if (sock >= 0) break;
848 *status = 4; /* dsn is already filled in */
852 /* Process the SMTP greeting from the server */
853 if (ml_sock_gets(sock, buf) < 0) {
855 strcpy(dsn, "Connection broken during SMTP conversation");
858 lprintf(9, "<%s\n", buf);
862 safestrncpy(dsn, &buf[4], 1023);
867 safestrncpy(dsn, &buf[4], 1023);
872 /* At this point we know we are talking to a real SMTP server */
874 /* Do a HELO command */
875 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
876 lprintf(9, ">%s", buf);
877 sock_write(sock, buf, strlen(buf));
878 if (ml_sock_gets(sock, buf) < 0) {
880 strcpy(dsn, "Connection broken during SMTP HELO");
883 lprintf(9, "<%s\n", buf);
887 safestrncpy(dsn, &buf[4], 1023);
892 safestrncpy(dsn, &buf[4], 1023);
898 /* HELO succeeded, now try the MAIL From: command */
899 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
900 lprintf(9, ">%s", buf);
901 sock_write(sock, buf, strlen(buf));
902 if (ml_sock_gets(sock, buf) < 0) {
904 strcpy(dsn, "Connection broken during SMTP MAIL");
907 lprintf(9, "<%s\n", buf);
911 safestrncpy(dsn, &buf[4], 1023);
916 safestrncpy(dsn, &buf[4], 1023);
922 /* MAIL succeeded, now try the RCPT To: command */
923 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
924 lprintf(9, ">%s", buf);
925 sock_write(sock, buf, strlen(buf));
926 if (ml_sock_gets(sock, buf) < 0) {
928 strcpy(dsn, "Connection broken during SMTP RCPT");
931 lprintf(9, "<%s\n", buf);
935 safestrncpy(dsn, &buf[4], 1023);
940 safestrncpy(dsn, &buf[4], 1023);
946 /* RCPT succeeded, now try the DATA command */
947 lprintf(9, ">DATA\n");
948 sock_write(sock, "DATA\r\n", 6);
949 if (ml_sock_gets(sock, buf) < 0) {
951 strcpy(dsn, "Connection broken during SMTP DATA");
954 lprintf(9, "<%s\n", buf);
958 safestrncpy(dsn, &buf[4], 1023);
963 safestrncpy(dsn, &buf[4], 1023);
968 /* If we reach this point, the server is expecting data */
970 while (msg_size > 0) {
971 blocksize = sizeof(buf);
972 if (blocksize > msg_size) blocksize = msg_size;
973 fread(buf, blocksize, 1, msg_fp);
974 sock_write(sock, buf, blocksize);
975 msg_size -= blocksize;
977 if (buf[blocksize-1] != 10) {
978 lprintf(5, "Possible problem: message did not correctly "
979 "terminate. (expecting 0x10, got 0x%02x)\n",
983 sock_write(sock, ".\r\n", 3);
984 if (ml_sock_gets(sock, buf) < 0) {
986 strcpy(dsn, "Connection broken during SMTP message transmit");
989 lprintf(9, "%s\n", buf);
993 safestrncpy(dsn, &buf[4], 1023);
998 safestrncpy(dsn, &buf[4], 1023);
1004 safestrncpy(dsn, &buf[4], 1023);
1007 lprintf(9, ">QUIT\n");
1008 sock_write(sock, "QUIT\r\n", 6);
1009 ml_sock_gets(sock, buf);
1010 lprintf(9, "<%s\n", buf);
1012 bail: if (msg_fp != NULL) fclose(msg_fp);
1020 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1021 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1022 * a "bounce" message (delivery status notification).
1024 void smtp_do_bounce(char *instr) {
1032 char bounceto[1024];
1033 int num_bounces = 0;
1034 int bounce_this = 0;
1035 long bounce_msgid = (-1);
1036 time_t submitted = 0L;
1037 struct CtdlMessage *bmsg = NULL;
1041 lprintf(9, "smtp_do_bounce() called\n");
1042 strcpy(bounceto, "");
1044 lines = num_tokens(instr, '\n');
1047 /* See if it's time to give up on delivery of this message */
1048 for (i=0; i<lines; ++i) {
1049 extract_token(buf, instr, i, '\n');
1050 extract(key, buf, 0);
1051 extract(addr, buf, 1);
1052 if (!strcasecmp(key, "submitted")) {
1053 submitted = atol(addr);
1057 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1063 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1064 if (bmsg == NULL) return;
1065 memset(bmsg, 0, sizeof(struct CtdlMessage));
1067 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1068 bmsg->cm_anon_type = MES_NORMAL;
1069 bmsg->cm_format_type = 1;
1070 bmsg->cm_fields['A'] = strdoop("Citadel");
1071 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1072 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1074 if (give_up) bmsg->cm_fields['M'] = strdoop(
1075 "A message you sent could not be delivered to some or all of its recipients\n"
1076 "due to prolonged unavailability of its destination(s).\n"
1077 "Giving up on the following addresses:\n\n"
1080 else bmsg->cm_fields['M'] = strdoop(
1081 "A message you sent could not be delivered to some or all of its recipients.\n"
1082 "The following addresses were undeliverable:\n\n"
1086 * Now go through the instructions checking for stuff.
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);
1097 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1098 key, addr, status, dsn);
1100 if (!strcasecmp(key, "bounceto")) {
1101 strcpy(bounceto, addr);
1105 (!strcasecmp(key, "local"))
1106 || (!strcasecmp(key, "remote"))
1107 || (!strcasecmp(key, "ignet"))
1108 || (!strcasecmp(key, "room"))
1110 if (status == 5) bounce_this = 1;
1111 if (give_up) bounce_this = 1;
1117 if (bmsg->cm_fields['M'] == NULL) {
1118 lprintf(2, "ERROR ... M field is null "
1119 "(%s:%d)\n", __FILE__, __LINE__);
1122 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1123 strlen(bmsg->cm_fields['M']) + 1024 );
1124 strcat(bmsg->cm_fields['M'], addr);
1125 strcat(bmsg->cm_fields['M'], ": ");
1126 strcat(bmsg->cm_fields['M'], dsn);
1127 strcat(bmsg->cm_fields['M'], "\n");
1129 remove_token(instr, i, '\n');
1135 /* Deliver the bounce if there's anything worth mentioning */
1136 lprintf(9, "num_bounces = %d\n", num_bounces);
1137 if (num_bounces > 0) {
1139 /* First try the user who sent the message */
1140 lprintf(9, "bounce to user? <%s>\n", bounceto);
1142 if (strlen(bounceto) == 0) {
1143 lprintf(7, "No bounce address specified\n");
1144 bounce_msgid = (-1L);
1146 /* FIXME this won't work
1147 else if (mes_type = alias(bounceto), mes_type == MES_ERROR) {
1148 lprintf(7, "Invalid bounce address <%s>\n", bounceto);
1149 bounce_msgid = (-1L);
1152 bounce_msgid = CtdlSubmitMsg(bmsg,
1159 /* Otherwise, go to the Aide> room */
1160 lprintf(9, "bounce to room?\n");
1161 if (bounce_msgid < 0L) bounce_msgid = CtdlSubmitMsg(bmsg,
1165 CtdlFreeMessage(bmsg);
1166 lprintf(9, "Done processing bounces\n");
1171 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1172 * set of delivery instructions for completed deliveries and remove them.
1174 * It returns the number of incomplete deliveries remaining.
1176 int smtp_purge_completed_deliveries(char *instr) {
1187 lines = num_tokens(instr, '\n');
1188 for (i=0; i<lines; ++i) {
1189 extract_token(buf, instr, i, '\n');
1190 extract(key, buf, 0);
1191 extract(addr, buf, 1);
1192 status = extract_int(buf, 2);
1193 extract(dsn, buf, 3);
1198 (!strcasecmp(key, "local"))
1199 || (!strcasecmp(key, "remote"))
1200 || (!strcasecmp(key, "ignet"))
1201 || (!strcasecmp(key, "room"))
1203 if (status == 2) completed = 1;
1208 remove_token(instr, i, '\n');
1221 * Called by smtp_do_queue() to handle an individual message.
1223 void smtp_do_procmsg(long msgnum, void *userdata) {
1224 struct CtdlMessage *msg;
1226 char *results = NULL;
1234 long text_msgid = (-1);
1235 int incomplete_deliveries_remaining;
1236 time_t attempted = 0L;
1237 time_t last_attempted = 0L;
1238 time_t retry = SMTP_RETRY_INTERVAL;
1240 lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1242 msg = CtdlFetchMessage(msgnum);
1244 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1248 instr = strdoop(msg->cm_fields['M']);
1249 CtdlFreeMessage(msg);
1251 /* Strip out the headers amd any other non-instruction line */
1252 lines = num_tokens(instr, '\n');
1253 for (i=0; i<lines; ++i) {
1254 extract_token(buf, instr, i, '\n');
1255 if (num_tokens(buf, '|') < 2) {
1256 remove_token(instr, i, '\n');
1262 /* Learn the message ID and find out about recent delivery attempts */
1263 lines = num_tokens(instr, '\n');
1264 for (i=0; i<lines; ++i) {
1265 extract_token(buf, instr, i, '\n');
1266 extract(key, buf, 0);
1267 if (!strcasecmp(key, "msgid")) {
1268 text_msgid = extract_long(buf, 1);
1270 if (!strcasecmp(key, "retry")) {
1271 /* double the retry interval after each attempt */
1272 retry = extract_long(buf, 1) * 2L;
1273 if (retry > SMTP_RETRY_MAX) {
1274 retry = SMTP_RETRY_MAX;
1276 remove_token(instr, i, '\n');
1278 if (!strcasecmp(key, "attempted")) {
1279 attempted = extract_long(buf, 1);
1280 if (attempted > last_attempted)
1281 last_attempted = attempted;
1286 * Postpone delivery if we've already tried recently.
1288 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1289 lprintf(7, "Retry time not yet reached.\n");
1296 * Bail out if there's no actual message associated with this
1298 if (text_msgid < 0L) {
1299 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1304 /* Plow through the instructions looking for 'remote' directives and
1305 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1306 * were experienced and it's time to try again)
1308 lines = num_tokens(instr, '\n');
1309 for (i=0; i<lines; ++i) {
1310 extract_token(buf, instr, i, '\n');
1311 extract(key, buf, 0);
1312 extract(addr, buf, 1);
1313 status = extract_int(buf, 2);
1314 extract(dsn, buf, 3);
1315 if ( (!strcasecmp(key, "remote"))
1316 && ((status==0)||(status==3)||(status==4)) ) {
1317 remove_token(instr, i, '\n');
1320 lprintf(9, "SMTP: Trying <%s>\n", addr);
1321 smtp_try(key, addr, &status, dsn, text_msgid);
1323 if (results == NULL) {
1324 results = mallok(1024);
1325 memset(results, 0, 1024);
1328 results = reallok(results,
1329 strlen(results) + 1024);
1331 sprintf(&results[strlen(results)],
1333 key, addr, status, dsn);
1338 if (results != NULL) {
1339 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1340 strcat(instr, results);
1345 /* Generate 'bounce' messages */
1346 smtp_do_bounce(instr);
1348 /* Go through the delivery list, deleting completed deliveries */
1349 incomplete_deliveries_remaining =
1350 smtp_purge_completed_deliveries(instr);
1354 * No delivery instructions remain, so delete both the instructions
1355 * message and the message message.
1357 if (incomplete_deliveries_remaining <= 0) {
1358 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1359 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1364 * Uncompleted delivery instructions remain, so delete the old
1365 * instructions and replace with the updated ones.
1367 if (incomplete_deliveries_remaining > 0) {
1368 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1369 msg = mallok(sizeof(struct CtdlMessage));
1370 memset(msg, 0, sizeof(struct CtdlMessage));
1371 msg->cm_magic = CTDLMESSAGE_MAGIC;
1372 msg->cm_anon_type = MES_NORMAL;
1373 msg->cm_format_type = FMT_RFC822;
1374 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1375 snprintf(msg->cm_fields['M'],
1377 "Content-type: %s\n\n%s\n"
1380 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1382 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1383 CtdlFreeMessage(msg);
1393 * Run through the queue sending out messages.
1395 void smtp_do_queue(void) {
1396 static int doing_queue = 0;
1399 * This is a simple concurrency check to make sure only one queue run
1400 * is done at a time. We could do this with a mutex, but since we
1401 * don't really require extremely fine granularity here, we'll do it
1402 * with a static variable instead.
1404 if (doing_queue) return;
1408 * Go ahead and run the queue
1410 lprintf(7, "SMTP: processing outbound queue\n");
1412 if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1413 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1416 CtdlForEachMessage(MSGS_ALL, 0L, (-127),
1417 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1419 lprintf(7, "SMTP: queue run completed\n");
1426 /*****************************************************************************/
1427 /* SMTP UTILITY COMMANDS */
1428 /*****************************************************************************/
1430 void cmd_smtp(char *argbuf) {
1437 if (CtdlAccessCheck(ac_aide)) return;
1439 extract(cmd, argbuf, 0);
1441 if (!strcasecmp(cmd, "mx")) {
1442 extract(node, argbuf, 1);
1443 num_mxhosts = getmx(buf, node);
1444 cprintf("%d %d MX hosts listed for %s\n",
1445 LISTING_FOLLOWS, num_mxhosts, node);
1446 for (i=0; i<num_mxhosts; ++i) {
1447 extract(node, buf, i);
1448 cprintf("%s\n", node);
1454 else if (!strcasecmp(cmd, "runqueue")) {
1456 cprintf("%d All outbound SMTP will be retried now.\n", OK);
1461 cprintf("%d Invalid command.\n", ERROR+ILLEGAL_VALUE);
1469 /*****************************************************************************/
1470 /* MODULE INITIALIZATION STUFF */
1471 /*****************************************************************************/
1474 char *Dynamic_Module_Init(void)
1476 SYM_SMTP = CtdlGetDynamicSymbol();
1477 SYM_SMTP_RECP = CtdlGetDynamicSymbol();
1479 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1484 CtdlRegisterServiceHook(0, /* ...and locally */
1489 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1);
1490 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1491 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");