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));
764 if (sock >= 0) break;
768 *status = 4; /* dsn is already filled in */
772 /* Process the SMTP greeting from the server */
773 if (ml_sock_gets(sock, buf) < 0) {
775 strcpy(dsn, "Connection broken during SMTP conversation");
778 lprintf(9, "<%s\n", buf);
782 safestrncpy(dsn, &buf[4], 1023);
787 safestrncpy(dsn, &buf[4], 1023);
792 /* At this point we know we are talking to a real SMTP server */
794 /* Do a HELO command */
795 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
796 lprintf(9, ">%s", buf);
797 sock_write(sock, buf, strlen(buf));
798 if (ml_sock_gets(sock, buf) < 0) {
800 strcpy(dsn, "Connection broken during SMTP HELO");
803 lprintf(9, "<%s\n", buf);
807 safestrncpy(dsn, &buf[4], 1023);
812 safestrncpy(dsn, &buf[4], 1023);
818 /* HELO succeeded, now try the MAIL From: command */
819 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
820 lprintf(9, ">%s", buf);
821 sock_write(sock, buf, strlen(buf));
822 if (ml_sock_gets(sock, buf) < 0) {
824 strcpy(dsn, "Connection broken during SMTP MAIL");
827 lprintf(9, "<%s\n", buf);
831 safestrncpy(dsn, &buf[4], 1023);
836 safestrncpy(dsn, &buf[4], 1023);
842 /* MAIL succeeded, now try the RCPT To: command */
843 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
844 lprintf(9, ">%s", buf);
845 sock_write(sock, buf, strlen(buf));
846 if (ml_sock_gets(sock, buf) < 0) {
848 strcpy(dsn, "Connection broken during SMTP RCPT");
851 lprintf(9, "<%s\n", buf);
855 safestrncpy(dsn, &buf[4], 1023);
860 safestrncpy(dsn, &buf[4], 1023);
866 /* RCPT succeeded, now try the DATA command */
867 lprintf(9, ">DATA\n");
868 sock_write(sock, "DATA\r\n", 6);
869 if (ml_sock_gets(sock, buf) < 0) {
871 strcpy(dsn, "Connection broken during SMTP DATA");
874 lprintf(9, "<%s\n", buf);
878 safestrncpy(dsn, &buf[4], 1023);
883 safestrncpy(dsn, &buf[4], 1023);
888 /* If we reach this point, the server is expecting data */
890 while (msg_size > 0) {
891 blocksize = sizeof(buf);
892 if (blocksize > msg_size) blocksize = msg_size;
893 fread(buf, blocksize, 1, msg_fp);
894 sock_write(sock, buf, blocksize);
895 msg_size -= blocksize;
897 if (buf[blocksize-1] != 10) {
898 lprintf(5, "Possible problem: message did not correctly "
899 "terminate. (expecting 0x10, got 0x%02x)\n",
903 sock_write(sock, ".\r\n", 3);
904 if (ml_sock_gets(sock, buf) < 0) {
906 strcpy(dsn, "Connection broken during SMTP message transmit");
909 lprintf(9, "%s\n", buf);
913 safestrncpy(dsn, &buf[4], 1023);
918 safestrncpy(dsn, &buf[4], 1023);
924 safestrncpy(dsn, &buf[4], 1023);
927 lprintf(9, ">QUIT\n");
928 sock_write(sock, "QUIT\r\n", 6);
929 ml_sock_gets(sock, buf);
930 lprintf(9, "<%s\n", buf);
932 bail: if (msg_fp != NULL) fclose(msg_fp);
940 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
941 * instructions for "5" codes (permanent fatal errors) and produce/deliver
942 * a "bounce" message (delivery status notification).
944 void smtp_do_bounce(char *instr) {
955 long bounce_msgid = (-1);
956 time_t submitted = 0L;
957 struct CtdlMessage *bmsg = NULL;
959 struct recptypes *valid;
960 int successful_bounce = 0;
962 lprintf(9, "smtp_do_bounce() called\n");
963 strcpy(bounceto, "");
965 lines = num_tokens(instr, '\n');
968 /* See if it's time to give up on delivery of this message */
969 for (i=0; i<lines; ++i) {
970 extract_token(buf, instr, i, '\n');
971 extract(key, buf, 0);
972 extract(addr, buf, 1);
973 if (!strcasecmp(key, "submitted")) {
974 submitted = atol(addr);
978 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
984 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
985 if (bmsg == NULL) return;
986 memset(bmsg, 0, sizeof(struct CtdlMessage));
988 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
989 bmsg->cm_anon_type = MES_NORMAL;
990 bmsg->cm_format_type = 1;
991 bmsg->cm_fields['A'] = strdoop("Citadel");
992 bmsg->cm_fields['O'] = strdoop(MAILROOM);
993 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
995 if (give_up) bmsg->cm_fields['M'] = strdoop(
996 "A message you sent could not be delivered to some or all of its recipients\n"
997 "due to prolonged unavailability of its destination(s).\n"
998 "Giving up on the following addresses:\n\n"
1001 else bmsg->cm_fields['M'] = strdoop(
1002 "A message you sent could not be delivered to some or all of its recipients.\n"
1003 "The following addresses were undeliverable:\n\n"
1007 * Now go through the instructions checking for stuff.
1010 for (i=0; i<lines; ++i) {
1011 extract_token(buf, instr, i, '\n');
1012 extract(key, buf, 0);
1013 extract(addr, buf, 1);
1014 status = extract_int(buf, 2);
1015 extract(dsn, buf, 3);
1018 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1019 key, addr, status, dsn);
1021 if (!strcasecmp(key, "bounceto")) {
1022 strcpy(bounceto, addr);
1026 (!strcasecmp(key, "local"))
1027 || (!strcasecmp(key, "remote"))
1028 || (!strcasecmp(key, "ignet"))
1029 || (!strcasecmp(key, "room"))
1031 if (status == 5) bounce_this = 1;
1032 if (give_up) bounce_this = 1;
1038 if (bmsg->cm_fields['M'] == NULL) {
1039 lprintf(2, "ERROR ... M field is null "
1040 "(%s:%d)\n", __FILE__, __LINE__);
1043 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1044 strlen(bmsg->cm_fields['M']) + 1024 );
1045 strcat(bmsg->cm_fields['M'], addr);
1046 strcat(bmsg->cm_fields['M'], ": ");
1047 strcat(bmsg->cm_fields['M'], dsn);
1048 strcat(bmsg->cm_fields['M'], "\n");
1050 remove_token(instr, i, '\n');
1056 /* Deliver the bounce if there's anything worth mentioning */
1057 lprintf(9, "num_bounces = %d\n", num_bounces);
1058 if (num_bounces > 0) {
1060 /* First try the user who sent the message */
1061 lprintf(9, "bounce to user? <%s>\n", bounceto);
1062 if (strlen(bounceto) == 0) {
1063 lprintf(7, "No bounce address specified\n");
1064 bounce_msgid = (-1L);
1067 /* Can we deliver the bounce to the original sender? */
1068 valid = validate_recipients(bounceto);
1069 if (valid != NULL) {
1070 if (valid->num_error == 0) {
1071 CtdlSubmitMsg(bmsg, valid, "");
1072 successful_bounce = 1;
1076 /* If not, post it in the Aide> room */
1077 if (successful_bounce == 0) {
1078 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1081 /* Free up the memory we used */
1082 if (valid != NULL) {
1087 CtdlFreeMessage(bmsg);
1088 lprintf(9, "Done processing bounces\n");
1093 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1094 * set of delivery instructions for completed deliveries and remove them.
1096 * It returns the number of incomplete deliveries remaining.
1098 int smtp_purge_completed_deliveries(char *instr) {
1109 lines = num_tokens(instr, '\n');
1110 for (i=0; i<lines; ++i) {
1111 extract_token(buf, instr, i, '\n');
1112 extract(key, buf, 0);
1113 extract(addr, buf, 1);
1114 status = extract_int(buf, 2);
1115 extract(dsn, buf, 3);
1120 (!strcasecmp(key, "local"))
1121 || (!strcasecmp(key, "remote"))
1122 || (!strcasecmp(key, "ignet"))
1123 || (!strcasecmp(key, "room"))
1125 if (status == 2) completed = 1;
1130 remove_token(instr, i, '\n');
1143 * Called by smtp_do_queue() to handle an individual message.
1145 void smtp_do_procmsg(long msgnum, void *userdata) {
1146 struct CtdlMessage *msg;
1148 char *results = NULL;
1156 long text_msgid = (-1);
1157 int incomplete_deliveries_remaining;
1158 time_t attempted = 0L;
1159 time_t last_attempted = 0L;
1160 time_t retry = SMTP_RETRY_INTERVAL;
1162 lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1164 msg = CtdlFetchMessage(msgnum);
1166 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1170 instr = strdoop(msg->cm_fields['M']);
1171 CtdlFreeMessage(msg);
1173 /* Strip out the headers amd any other non-instruction line */
1174 lines = num_tokens(instr, '\n');
1175 for (i=0; i<lines; ++i) {
1176 extract_token(buf, instr, i, '\n');
1177 if (num_tokens(buf, '|') < 2) {
1178 remove_token(instr, i, '\n');
1184 /* Learn the message ID and find out about recent delivery attempts */
1185 lines = num_tokens(instr, '\n');
1186 for (i=0; i<lines; ++i) {
1187 extract_token(buf, instr, i, '\n');
1188 extract(key, buf, 0);
1189 if (!strcasecmp(key, "msgid")) {
1190 text_msgid = extract_long(buf, 1);
1192 if (!strcasecmp(key, "retry")) {
1193 /* double the retry interval after each attempt */
1194 retry = extract_long(buf, 1) * 2L;
1195 if (retry > SMTP_RETRY_MAX) {
1196 retry = SMTP_RETRY_MAX;
1198 remove_token(instr, i, '\n');
1200 if (!strcasecmp(key, "attempted")) {
1201 attempted = extract_long(buf, 1);
1202 if (attempted > last_attempted)
1203 last_attempted = attempted;
1208 * Postpone delivery if we've already tried recently.
1210 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1211 lprintf(7, "Retry time not yet reached.\n");
1218 * Bail out if there's no actual message associated with this
1220 if (text_msgid < 0L) {
1221 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1226 /* Plow through the instructions looking for 'remote' directives and
1227 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1228 * were experienced and it's time to try again)
1230 lines = num_tokens(instr, '\n');
1231 for (i=0; i<lines; ++i) {
1232 extract_token(buf, instr, i, '\n');
1233 extract(key, buf, 0);
1234 extract(addr, buf, 1);
1235 status = extract_int(buf, 2);
1236 extract(dsn, buf, 3);
1237 if ( (!strcasecmp(key, "remote"))
1238 && ((status==0)||(status==3)||(status==4)) ) {
1239 remove_token(instr, i, '\n');
1242 lprintf(9, "SMTP: Trying <%s>\n", addr);
1243 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1245 if (results == NULL) {
1246 results = mallok(1024);
1247 memset(results, 0, 1024);
1250 results = reallok(results,
1251 strlen(results) + 1024);
1253 snprintf(&results[strlen(results)], 1024,
1255 key, addr, status, dsn);
1260 if (results != NULL) {
1261 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1262 strcat(instr, results);
1267 /* Generate 'bounce' messages */
1268 smtp_do_bounce(instr);
1270 /* Go through the delivery list, deleting completed deliveries */
1271 incomplete_deliveries_remaining =
1272 smtp_purge_completed_deliveries(instr);
1276 * No delivery instructions remain, so delete both the instructions
1277 * message and the message message.
1279 if (incomplete_deliveries_remaining <= 0) {
1280 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1281 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1286 * Uncompleted delivery instructions remain, so delete the old
1287 * instructions and replace with the updated ones.
1289 if (incomplete_deliveries_remaining > 0) {
1290 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1291 msg = mallok(sizeof(struct CtdlMessage));
1292 memset(msg, 0, sizeof(struct CtdlMessage));
1293 msg->cm_magic = CTDLMESSAGE_MAGIC;
1294 msg->cm_anon_type = MES_NORMAL;
1295 msg->cm_format_type = FMT_RFC822;
1296 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1297 snprintf(msg->cm_fields['M'],
1299 "Content-type: %s\n\n%s\n"
1302 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1304 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1305 CtdlFreeMessage(msg);
1315 * Run through the queue sending out messages.
1317 void smtp_do_queue(void) {
1318 static int doing_queue = 0;
1321 * This is a simple concurrency check to make sure only one queue run
1322 * is done at a time. We could do this with a mutex, but since we
1323 * don't really require extremely fine granularity here, we'll do it
1324 * with a static variable instead.
1326 if (doing_queue) return;
1330 * Go ahead and run the queue
1332 lprintf(7, "SMTP: processing outbound queue\n");
1334 if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1335 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1338 CtdlForEachMessage(MSGS_ALL, 0L,
1339 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1341 lprintf(7, "SMTP: queue run completed\n");
1348 /*****************************************************************************/
1349 /* SMTP UTILITY COMMANDS */
1350 /*****************************************************************************/
1352 void cmd_smtp(char *argbuf) {
1359 if (CtdlAccessCheck(ac_aide)) return;
1361 extract(cmd, argbuf, 0);
1363 if (!strcasecmp(cmd, "mx")) {
1364 extract(node, argbuf, 1);
1365 num_mxhosts = getmx(buf, node);
1366 cprintf("%d %d MX hosts listed for %s\n",
1367 LISTING_FOLLOWS, num_mxhosts, node);
1368 for (i=0; i<num_mxhosts; ++i) {
1369 extract(node, buf, i);
1370 cprintf("%s\n", node);
1376 else if (!strcasecmp(cmd, "runqueue")) {
1378 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1383 cprintf("%d Invalid command.\n", ERROR+ILLEGAL_VALUE);
1390 * Initialize the SMTP outbound queue
1392 void smtp_init_spoolout(void) {
1393 struct quickroom qrbuf;
1396 * Create the room. This will silently fail if the room already
1397 * exists, and that's perfectly ok, because we want it to exist.
1399 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0);
1402 * Make sure it's set to be a "system room" so it doesn't show up
1403 * in the <K>nown rooms list for Aides.
1405 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1406 qrbuf.QRflags2 |= QR2_SYSTEM;
1414 /*****************************************************************************/
1415 /* MODULE INITIALIZATION STUFF */
1416 /*****************************************************************************/
1419 char *serv_smtp_init(void)
1421 SYM_SMTP = CtdlGetDynamicSymbol();
1423 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1428 CtdlRegisterServiceHook(0, /* ...and locally */
1433 smtp_init_spoolout();
1434 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1435 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");