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>
34 #include <sys/socket.h>
35 #include <netinet/in.h>
36 #include <arpa/inet.h>
39 #include "sysdep_decls.h"
40 #include "citserver.h"
44 #include "serv_extensions.h"
51 #include "internet_addressing.h"
54 #include "clientsocket.h"
55 #include "locate_host.h"
62 struct citsmtp { /* Information about the current session */
65 struct usersupp vrfy_buffer;
70 int number_of_recipients;
73 int message_originated_locally;
76 enum { /* Command states for login authentication */
82 enum { /* Delivery modes */
87 #define SMTP ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
88 #define SMTP_RECPS ((char *)CtdlGetUserData(SYM_SMTP_RECPS))
89 #define SMTP_ROOMS ((char *)CtdlGetUserData(SYM_SMTP_ROOMS))
95 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
99 /*****************************************************************************/
100 /* SMTP SERVER (INBOUND) STUFF */
101 /*****************************************************************************/
107 * Here's where our SMTP session begins its happy day.
109 void smtp_greeting(void) {
111 strcpy(CC->cs_clientname, "SMTP session");
112 CC->internal_pgm = 1;
113 CC->cs_flags |= CS_STEALTH;
114 CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
115 CtdlAllocUserData(SYM_SMTP_RECPS, SIZ);
116 CtdlAllocUserData(SYM_SMTP_ROOMS, SIZ);
117 snprintf(SMTP_RECPS, SIZ, "%s", "");
118 snprintf(SMTP_ROOMS, SIZ, "%s", "");
120 cprintf("220 %s ESMTP Citadel/UX server ready.\r\n", config.c_fqdn);
125 * Implement HELO and EHLO commands.
127 void smtp_hello(char *argbuf, int is_esmtp) {
129 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
132 cprintf("250 Greetings and joyous salutations.\r\n");
135 cprintf("250-Greetings and joyous salutations.\r\n");
136 cprintf("250-HELP\r\n");
137 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
138 cprintf("250-PIPELINING\r\n");
139 cprintf("250 AUTH=LOGIN\r\n");
145 * Implement HELP command.
147 void smtp_help(void) {
148 cprintf("214-Commands accepted:\r\n");
149 cprintf("214- DATA\r\n");
150 cprintf("214- EHLO\r\n");
151 cprintf("214- EXPN\r\n");
152 cprintf("214- HELO\r\n");
153 cprintf("214- HELP\r\n");
154 cprintf("214- MAIL\r\n");
155 cprintf("214- NOOP\r\n");
156 cprintf("214- QUIT\r\n");
157 cprintf("214- RCPT\r\n");
158 cprintf("214- RSET\r\n");
159 cprintf("214- VRFY\r\n");
167 void smtp_get_user(char *argbuf) {
171 CtdlDecodeBase64(username, argbuf, SIZ);
172 lprintf(9, "Trying <%s>\n", username);
173 if (CtdlLoginExistingUser(username) == login_ok) {
174 CtdlEncodeBase64(buf, "Password:", 9);
175 cprintf("334 %s\r\n", buf);
176 SMTP->command_state = smtp_password;
179 cprintf("500 No such user.\r\n");
180 SMTP->command_state = smtp_command;
188 void smtp_get_pass(char *argbuf) {
191 CtdlDecodeBase64(password, argbuf, SIZ);
192 lprintf(9, "Trying <%s>\n", password);
193 if (CtdlTryPassword(password) == pass_ok) {
194 cprintf("235 Hello, %s\r\n", CC->usersupp.fullname);
195 lprintf(9, "SMTP authenticated login successful\n");
196 CC->internal_pgm = 0;
197 CC->cs_flags &= ~CS_STEALTH;
200 cprintf("500 Authentication failed.\r\n");
202 SMTP->command_state = smtp_command;
209 void smtp_auth(char *argbuf) {
212 if (strncasecmp(argbuf, "login", 5) ) {
213 cprintf("550 We only support LOGIN authentication.\r\n");
217 if (strlen(argbuf) >= 7) {
218 smtp_get_user(&argbuf[6]);
222 CtdlEncodeBase64(buf, "Username:", 9);
223 cprintf("334 %s\r\n", buf);
224 SMTP->command_state = smtp_user;
230 * Back end for smtp_vrfy() command
232 void smtp_vrfy_backend(struct usersupp *us, void *data) {
234 if (!fuzzy_match(us, SMTP->vrfy_match)) {
236 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
242 * Implements the VRFY (verify user name) command.
243 * Performs fuzzy match on full user names.
245 void smtp_vrfy(char *argbuf) {
246 SMTP->vrfy_count = 0;
247 strcpy(SMTP->vrfy_match, argbuf);
248 ForEachUser(smtp_vrfy_backend, NULL);
250 if (SMTP->vrfy_count < 1) {
251 cprintf("550 String does not match anything.\r\n");
253 else if (SMTP->vrfy_count == 1) {
254 cprintf("250 %s <cit%ld@%s>\r\n",
255 SMTP->vrfy_buffer.fullname,
256 SMTP->vrfy_buffer.usernum,
259 else if (SMTP->vrfy_count > 1) {
260 cprintf("553 Request ambiguous: %d users matched.\r\n",
269 * Back end for smtp_expn() command
271 void smtp_expn_backend(struct usersupp *us, void *data) {
273 if (!fuzzy_match(us, SMTP->vrfy_match)) {
275 if (SMTP->vrfy_count >= 1) {
276 cprintf("250-%s <cit%ld@%s>\r\n",
277 SMTP->vrfy_buffer.fullname,
278 SMTP->vrfy_buffer.usernum,
283 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
289 * Implements the EXPN (expand user name) command.
290 * Performs fuzzy match on full user names.
292 void smtp_expn(char *argbuf) {
293 SMTP->vrfy_count = 0;
294 strcpy(SMTP->vrfy_match, argbuf);
295 ForEachUser(smtp_expn_backend, NULL);
297 if (SMTP->vrfy_count < 1) {
298 cprintf("550 String does not match anything.\r\n");
300 else if (SMTP->vrfy_count >= 1) {
301 cprintf("250 %s <cit%ld@%s>\r\n",
302 SMTP->vrfy_buffer.fullname,
303 SMTP->vrfy_buffer.usernum,
310 * Implements the RSET (reset state) command.
311 * Currently this just zeroes out the state buffer. If pointers to data
312 * allocated with mallok() are ever placed in the state buffer, we have to
313 * be sure to phree() them first!
315 void smtp_rset(void) {
316 memset(SMTP, 0, sizeof(struct citsmtp));
319 * It is somewhat ambiguous whether we want to log out when a RSET
320 * command is issued. Here's the code to do it. It is commented out
321 * because some clients (such as Pine) issue RSET commands before
322 * each message, but still expect to be logged in.
324 * if (CC->logged_in) {
329 cprintf("250 Zap!\r\n");
333 * Clear out the portions of the state buffer that need to be cleared out
334 * after the DATA command finishes.
336 void smtp_data_clear(void) {
337 strcpy(SMTP->from, "");
338 strcpy(SMTP->recipients, "");
339 SMTP->number_of_recipients = 0;
340 SMTP->delivery_mode = 0;
341 SMTP->message_originated_locally = 0;
347 * Implements the "MAIL From:" command
349 void smtp_mail(char *argbuf) {
354 if (strlen(SMTP->from) != 0) {
355 cprintf("503 Only one sender permitted\r\n");
359 if (strncasecmp(argbuf, "From:", 5)) {
360 cprintf("501 Syntax error\r\n");
364 strcpy(SMTP->from, &argbuf[5]);
366 stripallbut(SMTP->from, '<', '>');
368 if (strlen(SMTP->from) == 0) {
369 cprintf("501 Empty sender name is not permitted\r\n");
373 /* If this SMTP connection is from a logged-in user, force the 'from'
374 * to be the user's Internet e-mail address as Citadel knows it.
377 strcpy(SMTP->from, CC->cs_inet_email);
378 cprintf("250 Sender ok <%s>\r\n", SMTP->from);
379 SMTP->message_originated_locally = 1;
383 /* Otherwise, make sure outsiders aren't trying to forge mail from
387 process_rfc822_addr(SMTP->from, user, node, name);
388 if (CtdlHostAlias(node) != hostalias_nomatch) {
389 cprintf("550 You must log in to send mail from %s\r\n",
391 strcpy(SMTP->from, "");
396 cprintf("250 Sender ok\r\n");
402 * Implements the "RCPT To:" command
404 void smtp_rcpt(char *argbuf) {
406 char message_to_spammer[SIZ];
407 struct recptypes *valid = NULL;
409 if (strlen(SMTP->from) == 0) {
410 cprintf("503 Need MAIL before RCPT\r\n");
414 if (strncasecmp(argbuf, "To:", 3)) {
415 cprintf("501 Syntax error\r\n");
419 strcpy(recp, &argbuf[3]);
421 stripallbut(recp, '<', '>');
423 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
424 cprintf("452 Too many recipients\r\n");
429 if ( (!CC->logged_in) && (!CC->is_local_socket) ) {
430 if (rbl_check(message_to_spammer)) {
431 cprintf("550 %s\r\n", message_to_spammer);
432 /* no need to phree(valid), it's not allocated yet */
437 valid = validate_recipients(recp);
438 if (valid->num_error > 0) {
439 cprintf("599 Error: %s\r\n", valid->errormsg);
444 if (valid->num_internet > 0) {
445 if (SMTP->message_originated_locally == 0) {
446 cprintf("551 Relaying denied <%s>\r\n", recp);
452 cprintf("250 RCPT ok <%s>\r\n", recp);
453 if (strlen(SMTP->recipients) > 0) {
454 strcat(SMTP->recipients, ",");
456 strcat(SMTP->recipients, recp);
457 SMTP->number_of_recipients += 1;
464 * Implements the DATA command
466 void smtp_data(void) {
468 struct CtdlMessage *msg;
471 struct recptypes *valid;
474 if (strlen(SMTP->from) == 0) {
475 cprintf("503 Need MAIL command first.\r\n");
479 if (SMTP->number_of_recipients < 1) {
480 cprintf("503 Need RCPT command first.\r\n");
484 cprintf("354 Transmit message now; terminate with '.' by itself\r\n");
486 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
490 it should be Received: from %s (real.name.dom [w.x.y.z])
492 if (body != NULL) snprintf(body, 4096,
493 "Received: from %s (%s)\n"
500 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
502 cprintf("550 Unable to save message: internal error.\r\n");
506 lprintf(9, "Converting message...\n");
507 msg = convert_internet_message(body);
509 /* If the user is locally authenticated, FORCE the From: header to
510 * show up as the real sender. Yes, this violates the RFC standard,
511 * but IT MAKES SENSE. Comment it out if you don't like this behavior.
513 * We also set the "message room name" ('O' field) to MAILROOM
514 * (which is Mail> on most systems) to prevent it from getting set
515 * to something ugly like "0000058008.Sent Items>" when the message
516 * is read with a Citadel client.
519 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
520 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
521 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
522 if (msg->cm_fields['F'] != NULL) phree(msg->cm_fields['F']);
523 if (msg->cm_fields['O'] != NULL) phree(msg->cm_fields['O']);
524 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
525 msg->cm_fields['N'] = strdoop(config.c_nodename);
526 msg->cm_fields['H'] = strdoop(config.c_humannode);
527 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
528 msg->cm_fields['O'] = strdoop(MAILROOM);
531 /* Submit the message into the Citadel system. */
532 valid = validate_recipients(SMTP->recipients);
534 /* If there are modules that want to scan this message before final
535 * submission (such as virus checkers or spam filters), call them now
536 * and give them an opportunity to reject the message.
538 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
540 if (scan_errors > 0) { /* We don't want this message! */
542 if (msg->cm_fields['0'] == NULL) {
543 msg->cm_fields['0'] = strdoop(
544 "Message rejected by filter");
547 cprintf("550 %s\r\n", msg->cm_fields['0']);
550 else { /* Ok, we'll accept this message. */
551 msgnum = CtdlSubmitMsg(msg, valid, "");
553 cprintf("250 Message accepted.\r\n");
556 cprintf("550 Internal delivery error\r\n");
560 CtdlFreeMessage(msg);
562 smtp_data_clear(); /* clear out the buffers now */
569 * Main command loop for SMTP sessions.
571 void smtp_command_loop(void) {
575 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
576 if (client_gets(cmdbuf) < 1) {
577 lprintf(3, "SMTP socket is broken. Ending session.\n");
581 lprintf(5, "SMTP: %s\n", cmdbuf);
582 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
584 lprintf(9, "CC->logged_in = %d\n", CC->logged_in);
586 if (SMTP->command_state == smtp_user) {
587 smtp_get_user(cmdbuf);
590 else if (SMTP->command_state == smtp_password) {
591 smtp_get_pass(cmdbuf);
594 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
595 smtp_auth(&cmdbuf[5]);
598 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
602 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
603 smtp_hello(&cmdbuf[5], 1);
606 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
607 smtp_expn(&cmdbuf[5]);
610 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
611 smtp_hello(&cmdbuf[5], 0);
614 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
618 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
619 smtp_mail(&cmdbuf[5]);
622 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
623 cprintf("250 NOOP\r\n");
626 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
627 cprintf("221 Goodbye...\r\n");
632 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
633 smtp_rcpt(&cmdbuf[5]);
636 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
640 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
641 smtp_vrfy(&cmdbuf[5]);
645 cprintf("502 I'm afraid I can't do that.\r\n");
653 /*****************************************************************************/
654 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
655 /*****************************************************************************/
662 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
665 void smtp_try(const char *key, const char *addr, int *status,
666 char *dsn, size_t n, long msgnum)
673 char user[SIZ], node[SIZ], name[SIZ];
679 size_t blocksize = 0;
682 /* Parse out the host portion of the recipient address */
683 process_rfc822_addr(addr, user, node, name);
685 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
688 /* Load the message out of the database into a temp file */
690 if (msg_fp == NULL) {
692 snprintf(dsn, n, "Error creating temporary file");
696 CtdlRedirectOutput(msg_fp, -1);
697 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
698 CtdlRedirectOutput(NULL, -1);
699 fseek(msg_fp, 0L, SEEK_END);
700 msg_size = ftell(msg_fp);
704 /* Extract something to send later in the 'MAIL From:' command */
705 strcpy(mailfrom, "");
709 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
710 if (!strncasecmp(buf, "From:", 5)) {
711 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
713 for (i=0; i<strlen(mailfrom); ++i) {
714 if (!isprint(mailfrom[i])) {
715 strcpy(&mailfrom[i], &mailfrom[i+1]);
720 /* Strip out parenthesized names */
723 for (i=0; i<strlen(mailfrom); ++i) {
724 if (mailfrom[i] == '(') lp = i;
725 if (mailfrom[i] == ')') rp = i;
727 if ((lp>0)&&(rp>lp)) {
728 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
731 /* Prefer brokketized names */
734 for (i=0; i<strlen(mailfrom); ++i) {
735 if (mailfrom[i] == '<') lp = i;
736 if (mailfrom[i] == '>') rp = i;
738 if ( (lp>=0) && (rp>lp) ) {
740 strcpy(mailfrom, &mailfrom[lp]);
745 } while (scan_done == 0);
746 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
748 /* Figure out what mail exchanger host we have to connect to */
749 num_mxhosts = getmx(mxhosts, node);
750 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
751 if (num_mxhosts < 1) {
753 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
757 for (mx=0; mx<num_mxhosts; ++mx) {
758 extract(buf, mxhosts, mx);
759 lprintf(9, "Trying <%s>\n", buf);
760 sock = sock_connect(buf, "25", "tcp");
761 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
762 if (sock >= 0) lprintf(9, "Connected!\n");
763 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
767 *status = 4; /* dsn is already filled in */
771 /* Process the SMTP greeting from the server */
772 if (ml_sock_gets(sock, buf) < 0) {
774 strcpy(dsn, "Connection broken during SMTP conversation");
777 lprintf(9, "<%s\n", buf);
781 safestrncpy(dsn, &buf[4], 1023);
786 safestrncpy(dsn, &buf[4], 1023);
791 /* At this point we know we are talking to a real SMTP server */
793 /* Do a HELO command */
794 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
795 lprintf(9, ">%s", buf);
796 sock_write(sock, buf, strlen(buf));
797 if (ml_sock_gets(sock, buf) < 0) {
799 strcpy(dsn, "Connection broken during SMTP HELO");
802 lprintf(9, "<%s\n", buf);
806 safestrncpy(dsn, &buf[4], 1023);
811 safestrncpy(dsn, &buf[4], 1023);
817 /* HELO succeeded, now try the MAIL From: command */
818 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
819 lprintf(9, ">%s", buf);
820 sock_write(sock, buf, strlen(buf));
821 if (ml_sock_gets(sock, buf) < 0) {
823 strcpy(dsn, "Connection broken during SMTP MAIL");
826 lprintf(9, "<%s\n", buf);
830 safestrncpy(dsn, &buf[4], 1023);
835 safestrncpy(dsn, &buf[4], 1023);
841 /* MAIL succeeded, now try the RCPT To: command */
842 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
843 lprintf(9, ">%s", buf);
844 sock_write(sock, buf, strlen(buf));
845 if (ml_sock_gets(sock, buf) < 0) {
847 strcpy(dsn, "Connection broken during SMTP RCPT");
850 lprintf(9, "<%s\n", buf);
854 safestrncpy(dsn, &buf[4], 1023);
859 safestrncpy(dsn, &buf[4], 1023);
865 /* RCPT succeeded, now try the DATA command */
866 lprintf(9, ">DATA\n");
867 sock_write(sock, "DATA\r\n", 6);
868 if (ml_sock_gets(sock, buf) < 0) {
870 strcpy(dsn, "Connection broken during SMTP DATA");
873 lprintf(9, "<%s\n", buf);
877 safestrncpy(dsn, &buf[4], 1023);
882 safestrncpy(dsn, &buf[4], 1023);
887 /* If we reach this point, the server is expecting data */
889 while (msg_size > 0) {
890 blocksize = sizeof(buf);
891 if (blocksize > msg_size) blocksize = msg_size;
892 fread(buf, blocksize, 1, msg_fp);
893 sock_write(sock, buf, blocksize);
894 msg_size -= blocksize;
896 if (buf[blocksize-1] != 10) {
897 lprintf(5, "Possible problem: message did not correctly "
898 "terminate. (expecting 0x10, got 0x%02x)\n",
902 sock_write(sock, ".\r\n", 3);
903 if (ml_sock_gets(sock, buf) < 0) {
905 strcpy(dsn, "Connection broken during SMTP message transmit");
908 lprintf(9, "%s\n", buf);
912 safestrncpy(dsn, &buf[4], 1023);
917 safestrncpy(dsn, &buf[4], 1023);
923 safestrncpy(dsn, &buf[4], 1023);
926 lprintf(9, ">QUIT\n");
927 sock_write(sock, "QUIT\r\n", 6);
928 ml_sock_gets(sock, buf);
929 lprintf(9, "<%s\n", buf);
931 bail: if (msg_fp != NULL) fclose(msg_fp);
939 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
940 * instructions for "5" codes (permanent fatal errors) and produce/deliver
941 * a "bounce" message (delivery status notification).
943 void smtp_do_bounce(char *instr) {
954 long bounce_msgid = (-1);
955 time_t submitted = 0L;
956 struct CtdlMessage *bmsg = NULL;
958 struct recptypes *valid;
959 int successful_bounce = 0;
961 lprintf(9, "smtp_do_bounce() called\n");
962 strcpy(bounceto, "");
964 lines = num_tokens(instr, '\n');
967 /* See if it's time to give up on delivery of this message */
968 for (i=0; i<lines; ++i) {
969 extract_token(buf, instr, i, '\n');
970 extract(key, buf, 0);
971 extract(addr, buf, 1);
972 if (!strcasecmp(key, "submitted")) {
973 submitted = atol(addr);
977 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
983 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
984 if (bmsg == NULL) return;
985 memset(bmsg, 0, sizeof(struct CtdlMessage));
987 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
988 bmsg->cm_anon_type = MES_NORMAL;
989 bmsg->cm_format_type = 1;
990 bmsg->cm_fields['A'] = strdoop("Citadel");
991 bmsg->cm_fields['O'] = strdoop(MAILROOM);
992 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
994 if (give_up) bmsg->cm_fields['M'] = strdoop(
995 "A message you sent could not be delivered to some or all of its recipients\n"
996 "due to prolonged unavailability of its destination(s).\n"
997 "Giving up on the following addresses:\n\n"
1000 else bmsg->cm_fields['M'] = strdoop(
1001 "A message you sent could not be delivered to some or all of its recipients.\n"
1002 "The following addresses were undeliverable:\n\n"
1006 * Now go through the instructions checking for stuff.
1008 for (i=0; i<lines; ++i) {
1009 extract_token(buf, instr, i, '\n');
1010 extract(key, buf, 0);
1011 extract(addr, buf, 1);
1012 status = extract_int(buf, 2);
1013 extract(dsn, buf, 3);
1016 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1017 key, addr, status, dsn);
1019 if (!strcasecmp(key, "bounceto")) {
1020 strcpy(bounceto, addr);
1024 (!strcasecmp(key, "local"))
1025 || (!strcasecmp(key, "remote"))
1026 || (!strcasecmp(key, "ignet"))
1027 || (!strcasecmp(key, "room"))
1029 if (status == 5) bounce_this = 1;
1030 if (give_up) bounce_this = 1;
1036 if (bmsg->cm_fields['M'] == NULL) {
1037 lprintf(2, "ERROR ... M field is null "
1038 "(%s:%d)\n", __FILE__, __LINE__);
1041 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1042 strlen(bmsg->cm_fields['M']) + 1024 );
1043 strcat(bmsg->cm_fields['M'], addr);
1044 strcat(bmsg->cm_fields['M'], ": ");
1045 strcat(bmsg->cm_fields['M'], dsn);
1046 strcat(bmsg->cm_fields['M'], "\n");
1048 remove_token(instr, i, '\n');
1054 /* Deliver the bounce if there's anything worth mentioning */
1055 lprintf(9, "num_bounces = %d\n", num_bounces);
1056 if (num_bounces > 0) {
1058 /* First try the user who sent the message */
1059 lprintf(9, "bounce to user? <%s>\n", bounceto);
1060 if (strlen(bounceto) == 0) {
1061 lprintf(7, "No bounce address specified\n");
1062 bounce_msgid = (-1L);
1065 /* Can we deliver the bounce to the original sender? */
1066 valid = validate_recipients(bounceto);
1067 if (valid != NULL) {
1068 if (valid->num_error == 0) {
1069 CtdlSubmitMsg(bmsg, valid, "");
1070 successful_bounce = 1;
1074 /* If not, post it in the Aide> room */
1075 if (successful_bounce == 0) {
1076 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1079 /* Free up the memory we used */
1080 if (valid != NULL) {
1085 CtdlFreeMessage(bmsg);
1086 lprintf(9, "Done processing bounces\n");
1091 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1092 * set of delivery instructions for completed deliveries and remove them.
1094 * It returns the number of incomplete deliveries remaining.
1096 int smtp_purge_completed_deliveries(char *instr) {
1107 lines = num_tokens(instr, '\n');
1108 for (i=0; i<lines; ++i) {
1109 extract_token(buf, instr, i, '\n');
1110 extract(key, buf, 0);
1111 extract(addr, buf, 1);
1112 status = extract_int(buf, 2);
1113 extract(dsn, buf, 3);
1118 (!strcasecmp(key, "local"))
1119 || (!strcasecmp(key, "remote"))
1120 || (!strcasecmp(key, "ignet"))
1121 || (!strcasecmp(key, "room"))
1123 if (status == 2) completed = 1;
1128 remove_token(instr, i, '\n');
1141 * Called by smtp_do_queue() to handle an individual message.
1143 void smtp_do_procmsg(long msgnum, void *userdata) {
1144 struct CtdlMessage *msg;
1146 char *results = NULL;
1154 long text_msgid = (-1);
1155 int incomplete_deliveries_remaining;
1156 time_t attempted = 0L;
1157 time_t last_attempted = 0L;
1158 time_t retry = SMTP_RETRY_INTERVAL;
1160 lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1162 msg = CtdlFetchMessage(msgnum);
1164 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1168 instr = strdoop(msg->cm_fields['M']);
1169 CtdlFreeMessage(msg);
1171 /* Strip out the headers amd any other non-instruction line */
1172 lines = num_tokens(instr, '\n');
1173 for (i=0; i<lines; ++i) {
1174 extract_token(buf, instr, i, '\n');
1175 if (num_tokens(buf, '|') < 2) {
1176 remove_token(instr, i, '\n');
1182 /* Learn the message ID and find out about recent delivery attempts */
1183 lines = num_tokens(instr, '\n');
1184 for (i=0; i<lines; ++i) {
1185 extract_token(buf, instr, i, '\n');
1186 extract(key, buf, 0);
1187 if (!strcasecmp(key, "msgid")) {
1188 text_msgid = extract_long(buf, 1);
1190 if (!strcasecmp(key, "retry")) {
1191 /* double the retry interval after each attempt */
1192 retry = extract_long(buf, 1) * 2L;
1193 if (retry > SMTP_RETRY_MAX) {
1194 retry = SMTP_RETRY_MAX;
1196 remove_token(instr, i, '\n');
1198 if (!strcasecmp(key, "attempted")) {
1199 attempted = extract_long(buf, 1);
1200 if (attempted > last_attempted)
1201 last_attempted = attempted;
1206 * Postpone delivery if we've already tried recently.
1208 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1209 lprintf(7, "Retry time not yet reached.\n");
1216 * Bail out if there's no actual message associated with this
1218 if (text_msgid < 0L) {
1219 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1224 /* Plow through the instructions looking for 'remote' directives and
1225 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1226 * were experienced and it's time to try again)
1228 lines = num_tokens(instr, '\n');
1229 for (i=0; i<lines; ++i) {
1230 extract_token(buf, instr, i, '\n');
1231 extract(key, buf, 0);
1232 extract(addr, buf, 1);
1233 status = extract_int(buf, 2);
1234 extract(dsn, buf, 3);
1235 if ( (!strcasecmp(key, "remote"))
1236 && ((status==0)||(status==3)||(status==4)) ) {
1238 /* Remove this "remote" instruction from the set,
1239 * but replace the set's final newline if
1240 * remove_token() stripped it. It has to be there.
1242 remove_token(instr, i, '\n');
1243 if (instr[strlen(instr)-1] != '\n') {
1244 strcat(instr, "\n");
1249 lprintf(9, "SMTP: Trying <%s>\n", addr);
1250 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1252 if (results == NULL) {
1253 results = mallok(1024);
1254 memset(results, 0, 1024);
1257 results = reallok(results,
1258 strlen(results) + 1024);
1260 snprintf(&results[strlen(results)], 1024,
1262 key, addr, status, dsn);
1267 if (results != NULL) {
1268 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1269 strcat(instr, results);
1274 /* Generate 'bounce' messages */
1275 smtp_do_bounce(instr);
1277 /* Go through the delivery list, deleting completed deliveries */
1278 incomplete_deliveries_remaining =
1279 smtp_purge_completed_deliveries(instr);
1283 * No delivery instructions remain, so delete both the instructions
1284 * message and the message message.
1286 if (incomplete_deliveries_remaining <= 0) {
1287 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1288 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1293 * Uncompleted delivery instructions remain, so delete the old
1294 * instructions and replace with the updated ones.
1296 if (incomplete_deliveries_remaining > 0) {
1297 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1298 msg = mallok(sizeof(struct CtdlMessage));
1299 memset(msg, 0, sizeof(struct CtdlMessage));
1300 msg->cm_magic = CTDLMESSAGE_MAGIC;
1301 msg->cm_anon_type = MES_NORMAL;
1302 msg->cm_format_type = FMT_RFC822;
1303 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1304 snprintf(msg->cm_fields['M'],
1306 "Content-type: %s\n\n%s\n"
1309 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1311 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1312 CtdlFreeMessage(msg);
1322 * Run through the queue sending out messages.
1324 void smtp_do_queue(void) {
1325 static int doing_queue = 0;
1328 * This is a simple concurrency check to make sure only one queue run
1329 * is done at a time. We could do this with a mutex, but since we
1330 * don't really require extremely fine granularity here, we'll do it
1331 * with a static variable instead.
1333 if (doing_queue) return;
1337 * Go ahead and run the queue
1339 lprintf(7, "SMTP: processing outbound queue\n");
1341 if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1342 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1345 CtdlForEachMessage(MSGS_ALL, 0L,
1346 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1348 lprintf(7, "SMTP: queue run completed\n");
1355 /*****************************************************************************/
1356 /* SMTP UTILITY COMMANDS */
1357 /*****************************************************************************/
1359 void cmd_smtp(char *argbuf) {
1366 if (CtdlAccessCheck(ac_aide)) return;
1368 extract(cmd, argbuf, 0);
1370 if (!strcasecmp(cmd, "mx")) {
1371 extract(node, argbuf, 1);
1372 num_mxhosts = getmx(buf, node);
1373 cprintf("%d %d MX hosts listed for %s\n",
1374 LISTING_FOLLOWS, num_mxhosts, node);
1375 for (i=0; i<num_mxhosts; ++i) {
1376 extract(node, buf, i);
1377 cprintf("%s\n", node);
1383 else if (!strcasecmp(cmd, "runqueue")) {
1385 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1390 cprintf("%d Invalid command.\n", ERROR+ILLEGAL_VALUE);
1397 * Initialize the SMTP outbound queue
1399 void smtp_init_spoolout(void) {
1400 struct quickroom qrbuf;
1403 * Create the room. This will silently fail if the room already
1404 * exists, and that's perfectly ok, because we want it to exist.
1406 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0);
1409 * Make sure it's set to be a "system room" so it doesn't show up
1410 * in the <K>nown rooms list for Aides.
1412 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1413 qrbuf.QRflags2 |= QR2_SYSTEM;
1421 /*****************************************************************************/
1422 /* MODULE INITIALIZATION STUFF */
1423 /*****************************************************************************/
1426 char *serv_smtp_init(void)
1428 SYM_SMTP = CtdlGetDynamicSymbol();
1430 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1435 CtdlRegisterServiceHook(0, /* ...and locally */
1440 smtp_init_spoolout();
1441 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1442 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");