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("552 %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);
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.
514 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
515 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
516 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
517 if (msg->cm_fields['F'] != NULL) phree(msg->cm_fields['F']);
518 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
519 msg->cm_fields['N'] = strdoop(config.c_nodename);
520 msg->cm_fields['H'] = strdoop(config.c_humannode);
521 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
524 /* Submit the message into the Citadel system. */
525 valid = validate_recipients(SMTP->recipients);
527 /* If there are modules that want to scan this message before final
528 * submission (such as virus checkers or spam filters), call them now
529 * and give them an opportunity to reject the message.
531 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
533 if (scan_errors > 0) { /* We don't want this message! */
535 if (msg->cm_fields['0'] == NULL) {
536 msg->cm_fields['0'] = strdoop(
537 "Message rejected by filter");
540 cprintf("552 %s\r\n", msg->cm_fields['0']);
543 else { /* Ok, we'll accept this message. */
544 msgnum = CtdlSubmitMsg(msg, valid, "");
546 cprintf("250 Message accepted.\r\n");
549 cprintf("550 Internal delivery error\r\n");
553 CtdlFreeMessage(msg);
555 smtp_data_clear(); /* clear out the buffers now */
562 * Main command loop for SMTP sessions.
564 void smtp_command_loop(void) {
568 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
569 if (client_gets(cmdbuf) < 1) {
570 lprintf(3, "SMTP socket is broken. Ending session.\n");
574 lprintf(5, "SMTP: %s\n", cmdbuf);
575 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
577 lprintf(9, "CC->logged_in = %d\n", CC->logged_in);
579 if (SMTP->command_state == smtp_user) {
580 smtp_get_user(cmdbuf);
583 else if (SMTP->command_state == smtp_password) {
584 smtp_get_pass(cmdbuf);
587 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
588 smtp_auth(&cmdbuf[5]);
591 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
595 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
596 smtp_hello(&cmdbuf[5], 1);
599 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
600 smtp_expn(&cmdbuf[5]);
603 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
604 smtp_hello(&cmdbuf[5], 0);
607 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
611 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
612 smtp_mail(&cmdbuf[5]);
615 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
616 cprintf("250 NOOP\r\n");
619 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
620 cprintf("221 Goodbye...\r\n");
625 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
626 smtp_rcpt(&cmdbuf[5]);
629 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
633 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
634 smtp_vrfy(&cmdbuf[5]);
638 cprintf("502 I'm afraid I can't do that.\r\n");
646 /*****************************************************************************/
647 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
648 /*****************************************************************************/
655 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
658 void smtp_try(const char *key, const char *addr, int *status,
659 char *dsn, size_t n, long msgnum)
666 char user[SIZ], node[SIZ], name[SIZ];
672 size_t blocksize = 0;
675 /* Parse out the host portion of the recipient address */
676 process_rfc822_addr(addr, user, node, name);
678 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
681 /* Load the message out of the database into a temp file */
683 if (msg_fp == NULL) {
685 snprintf(dsn, n, "Error creating temporary file");
689 CtdlRedirectOutput(msg_fp, -1);
690 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
691 CtdlRedirectOutput(NULL, -1);
692 fseek(msg_fp, 0L, SEEK_END);
693 msg_size = ftell(msg_fp);
697 /* Extract something to send later in the 'MAIL From:' command */
698 strcpy(mailfrom, "");
702 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
703 if (!strncasecmp(buf, "From:", 5)) {
704 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
706 for (i=0; i<strlen(mailfrom); ++i) {
707 if (!isprint(mailfrom[i])) {
708 strcpy(&mailfrom[i], &mailfrom[i+1]);
713 /* Strip out parenthesized names */
716 for (i=0; i<strlen(mailfrom); ++i) {
717 if (mailfrom[i] == '(') lp = i;
718 if (mailfrom[i] == ')') rp = i;
720 if ((lp>0)&&(rp>lp)) {
721 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
724 /* Prefer brokketized names */
727 for (i=0; i<strlen(mailfrom); ++i) {
728 if (mailfrom[i] == '<') lp = i;
729 if (mailfrom[i] == '>') rp = i;
731 if ( (lp>=0) && (rp>lp) ) {
733 strcpy(mailfrom, &mailfrom[lp]);
738 } while (scan_done == 0);
739 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
741 /* Figure out what mail exchanger host we have to connect to */
742 num_mxhosts = getmx(mxhosts, node);
743 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
744 if (num_mxhosts < 1) {
746 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
750 for (mx=0; mx<num_mxhosts; ++mx) {
751 extract(buf, mxhosts, mx);
752 lprintf(9, "Trying <%s>\n", buf);
753 sock = sock_connect(buf, "25", "tcp");
754 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
755 if (sock >= 0) lprintf(9, "Connected!\n");
756 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
757 if (sock >= 0) break;
761 *status = 4; /* dsn is already filled in */
765 /* Process the SMTP greeting from the server */
766 if (ml_sock_gets(sock, buf) < 0) {
768 strcpy(dsn, "Connection broken during SMTP conversation");
771 lprintf(9, "<%s\n", buf);
775 safestrncpy(dsn, &buf[4], 1023);
780 safestrncpy(dsn, &buf[4], 1023);
785 /* At this point we know we are talking to a real SMTP server */
787 /* Do a HELO command */
788 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
789 lprintf(9, ">%s", buf);
790 sock_write(sock, buf, strlen(buf));
791 if (ml_sock_gets(sock, buf) < 0) {
793 strcpy(dsn, "Connection broken during SMTP HELO");
796 lprintf(9, "<%s\n", buf);
800 safestrncpy(dsn, &buf[4], 1023);
805 safestrncpy(dsn, &buf[4], 1023);
811 /* HELO succeeded, now try the MAIL From: command */
812 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
813 lprintf(9, ">%s", buf);
814 sock_write(sock, buf, strlen(buf));
815 if (ml_sock_gets(sock, buf) < 0) {
817 strcpy(dsn, "Connection broken during SMTP MAIL");
820 lprintf(9, "<%s\n", buf);
824 safestrncpy(dsn, &buf[4], 1023);
829 safestrncpy(dsn, &buf[4], 1023);
835 /* MAIL succeeded, now try the RCPT To: command */
836 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
837 lprintf(9, ">%s", buf);
838 sock_write(sock, buf, strlen(buf));
839 if (ml_sock_gets(sock, buf) < 0) {
841 strcpy(dsn, "Connection broken during SMTP RCPT");
844 lprintf(9, "<%s\n", buf);
848 safestrncpy(dsn, &buf[4], 1023);
853 safestrncpy(dsn, &buf[4], 1023);
859 /* RCPT succeeded, now try the DATA command */
860 lprintf(9, ">DATA\n");
861 sock_write(sock, "DATA\r\n", 6);
862 if (ml_sock_gets(sock, buf) < 0) {
864 strcpy(dsn, "Connection broken during SMTP DATA");
867 lprintf(9, "<%s\n", buf);
871 safestrncpy(dsn, &buf[4], 1023);
876 safestrncpy(dsn, &buf[4], 1023);
881 /* If we reach this point, the server is expecting data */
883 while (msg_size > 0) {
884 blocksize = sizeof(buf);
885 if (blocksize > msg_size) blocksize = msg_size;
886 fread(buf, blocksize, 1, msg_fp);
887 sock_write(sock, buf, blocksize);
888 msg_size -= blocksize;
890 if (buf[blocksize-1] != 10) {
891 lprintf(5, "Possible problem: message did not correctly "
892 "terminate. (expecting 0x10, got 0x%02x)\n",
896 sock_write(sock, ".\r\n", 3);
897 if (ml_sock_gets(sock, buf) < 0) {
899 strcpy(dsn, "Connection broken during SMTP message transmit");
902 lprintf(9, "%s\n", buf);
906 safestrncpy(dsn, &buf[4], 1023);
911 safestrncpy(dsn, &buf[4], 1023);
917 safestrncpy(dsn, &buf[4], 1023);
920 lprintf(9, ">QUIT\n");
921 sock_write(sock, "QUIT\r\n", 6);
922 ml_sock_gets(sock, buf);
923 lprintf(9, "<%s\n", buf);
925 bail: if (msg_fp != NULL) fclose(msg_fp);
933 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
934 * instructions for "5" codes (permanent fatal errors) and produce/deliver
935 * a "bounce" message (delivery status notification).
937 void smtp_do_bounce(char *instr) {
948 long bounce_msgid = (-1);
949 time_t submitted = 0L;
950 struct CtdlMessage *bmsg = NULL;
952 struct recptypes *valid;
953 int successful_bounce = 0;
955 lprintf(9, "smtp_do_bounce() called\n");
956 strcpy(bounceto, "");
958 lines = num_tokens(instr, '\n');
961 /* See if it's time to give up on delivery of this message */
962 for (i=0; i<lines; ++i) {
963 extract_token(buf, instr, i, '\n');
964 extract(key, buf, 0);
965 extract(addr, buf, 1);
966 if (!strcasecmp(key, "submitted")) {
967 submitted = atol(addr);
971 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
977 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
978 if (bmsg == NULL) return;
979 memset(bmsg, 0, sizeof(struct CtdlMessage));
981 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
982 bmsg->cm_anon_type = MES_NORMAL;
983 bmsg->cm_format_type = 1;
984 bmsg->cm_fields['A'] = strdoop("Citadel");
985 bmsg->cm_fields['O'] = strdoop(MAILROOM);
986 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
988 if (give_up) bmsg->cm_fields['M'] = strdoop(
989 "A message you sent could not be delivered to some or all of its recipients\n"
990 "due to prolonged unavailability of its destination(s).\n"
991 "Giving up on the following addresses:\n\n"
994 else bmsg->cm_fields['M'] = strdoop(
995 "A message you sent could not be delivered to some or all of its recipients.\n"
996 "The following addresses were undeliverable:\n\n"
1000 * Now go through the instructions checking for stuff.
1003 for (i=0; i<lines; ++i) {
1004 extract_token(buf, instr, i, '\n');
1005 extract(key, buf, 0);
1006 extract(addr, buf, 1);
1007 status = extract_int(buf, 2);
1008 extract(dsn, buf, 3);
1011 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1012 key, addr, status, dsn);
1014 if (!strcasecmp(key, "bounceto")) {
1015 strcpy(bounceto, addr);
1019 (!strcasecmp(key, "local"))
1020 || (!strcasecmp(key, "remote"))
1021 || (!strcasecmp(key, "ignet"))
1022 || (!strcasecmp(key, "room"))
1024 if (status == 5) bounce_this = 1;
1025 if (give_up) bounce_this = 1;
1031 if (bmsg->cm_fields['M'] == NULL) {
1032 lprintf(2, "ERROR ... M field is null "
1033 "(%s:%d)\n", __FILE__, __LINE__);
1036 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1037 strlen(bmsg->cm_fields['M']) + 1024 );
1038 strcat(bmsg->cm_fields['M'], addr);
1039 strcat(bmsg->cm_fields['M'], ": ");
1040 strcat(bmsg->cm_fields['M'], dsn);
1041 strcat(bmsg->cm_fields['M'], "\n");
1043 remove_token(instr, i, '\n');
1049 /* Deliver the bounce if there's anything worth mentioning */
1050 lprintf(9, "num_bounces = %d\n", num_bounces);
1051 if (num_bounces > 0) {
1053 /* First try the user who sent the message */
1054 lprintf(9, "bounce to user? <%s>\n", bounceto);
1055 if (strlen(bounceto) == 0) {
1056 lprintf(7, "No bounce address specified\n");
1057 bounce_msgid = (-1L);
1060 /* Can we deliver the bounce to the original sender? */
1061 valid = validate_recipients(bounceto);
1062 if (valid != NULL) {
1063 if (valid->num_error == 0) {
1064 CtdlSubmitMsg(bmsg, valid, "");
1065 successful_bounce = 1;
1069 /* If not, post it in the Aide> room */
1070 if (successful_bounce == 0) {
1071 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1074 /* Free up the memory we used */
1075 if (valid != NULL) {
1080 CtdlFreeMessage(bmsg);
1081 lprintf(9, "Done processing bounces\n");
1086 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1087 * set of delivery instructions for completed deliveries and remove them.
1089 * It returns the number of incomplete deliveries remaining.
1091 int smtp_purge_completed_deliveries(char *instr) {
1102 lines = num_tokens(instr, '\n');
1103 for (i=0; i<lines; ++i) {
1104 extract_token(buf, instr, i, '\n');
1105 extract(key, buf, 0);
1106 extract(addr, buf, 1);
1107 status = extract_int(buf, 2);
1108 extract(dsn, buf, 3);
1113 (!strcasecmp(key, "local"))
1114 || (!strcasecmp(key, "remote"))
1115 || (!strcasecmp(key, "ignet"))
1116 || (!strcasecmp(key, "room"))
1118 if (status == 2) completed = 1;
1123 remove_token(instr, i, '\n');
1136 * Called by smtp_do_queue() to handle an individual message.
1138 void smtp_do_procmsg(long msgnum, void *userdata) {
1139 struct CtdlMessage *msg;
1141 char *results = NULL;
1149 long text_msgid = (-1);
1150 int incomplete_deliveries_remaining;
1151 time_t attempted = 0L;
1152 time_t last_attempted = 0L;
1153 time_t retry = SMTP_RETRY_INTERVAL;
1155 lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1157 msg = CtdlFetchMessage(msgnum);
1159 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1163 instr = strdoop(msg->cm_fields['M']);
1164 CtdlFreeMessage(msg);
1166 /* Strip out the headers amd any other non-instruction line */
1167 lines = num_tokens(instr, '\n');
1168 for (i=0; i<lines; ++i) {
1169 extract_token(buf, instr, i, '\n');
1170 if (num_tokens(buf, '|') < 2) {
1171 remove_token(instr, i, '\n');
1177 /* Learn the message ID and find out about recent delivery attempts */
1178 lines = num_tokens(instr, '\n');
1179 for (i=0; i<lines; ++i) {
1180 extract_token(buf, instr, i, '\n');
1181 extract(key, buf, 0);
1182 if (!strcasecmp(key, "msgid")) {
1183 text_msgid = extract_long(buf, 1);
1185 if (!strcasecmp(key, "retry")) {
1186 /* double the retry interval after each attempt */
1187 retry = extract_long(buf, 1) * 2L;
1188 if (retry > SMTP_RETRY_MAX) {
1189 retry = SMTP_RETRY_MAX;
1191 remove_token(instr, i, '\n');
1193 if (!strcasecmp(key, "attempted")) {
1194 attempted = extract_long(buf, 1);
1195 if (attempted > last_attempted)
1196 last_attempted = attempted;
1201 * Postpone delivery if we've already tried recently.
1203 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1204 lprintf(7, "Retry time not yet reached.\n");
1211 * Bail out if there's no actual message associated with this
1213 if (text_msgid < 0L) {
1214 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1219 /* Plow through the instructions looking for 'remote' directives and
1220 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1221 * were experienced and it's time to try again)
1223 lines = num_tokens(instr, '\n');
1224 for (i=0; i<lines; ++i) {
1225 extract_token(buf, instr, i, '\n');
1226 extract(key, buf, 0);
1227 extract(addr, buf, 1);
1228 status = extract_int(buf, 2);
1229 extract(dsn, buf, 3);
1230 if ( (!strcasecmp(key, "remote"))
1231 && ((status==0)||(status==3)||(status==4)) ) {
1232 remove_token(instr, i, '\n');
1235 lprintf(9, "SMTP: Trying <%s>\n", addr);
1236 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1238 if (results == NULL) {
1239 results = mallok(1024);
1240 memset(results, 0, 1024);
1243 results = reallok(results,
1244 strlen(results) + 1024);
1246 snprintf(&results[strlen(results)], 1024,
1248 key, addr, status, dsn);
1253 if (results != NULL) {
1254 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1255 strcat(instr, results);
1260 /* Generate 'bounce' messages */
1261 smtp_do_bounce(instr);
1263 /* Go through the delivery list, deleting completed deliveries */
1264 incomplete_deliveries_remaining =
1265 smtp_purge_completed_deliveries(instr);
1269 * No delivery instructions remain, so delete both the instructions
1270 * message and the message message.
1272 if (incomplete_deliveries_remaining <= 0) {
1273 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1274 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1279 * Uncompleted delivery instructions remain, so delete the old
1280 * instructions and replace with the updated ones.
1282 if (incomplete_deliveries_remaining > 0) {
1283 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1284 msg = mallok(sizeof(struct CtdlMessage));
1285 memset(msg, 0, sizeof(struct CtdlMessage));
1286 msg->cm_magic = CTDLMESSAGE_MAGIC;
1287 msg->cm_anon_type = MES_NORMAL;
1288 msg->cm_format_type = FMT_RFC822;
1289 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1290 snprintf(msg->cm_fields['M'],
1292 "Content-type: %s\n\n%s\n"
1295 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1297 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1298 CtdlFreeMessage(msg);
1308 * Run through the queue sending out messages.
1310 void smtp_do_queue(void) {
1311 static int doing_queue = 0;
1314 * This is a simple concurrency check to make sure only one queue run
1315 * is done at a time. We could do this with a mutex, but since we
1316 * don't really require extremely fine granularity here, we'll do it
1317 * with a static variable instead.
1319 if (doing_queue) return;
1323 * Go ahead and run the queue
1325 lprintf(7, "SMTP: processing outbound queue\n");
1327 if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1328 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1331 CtdlForEachMessage(MSGS_ALL, 0L,
1332 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1334 lprintf(7, "SMTP: queue run completed\n");
1341 /*****************************************************************************/
1342 /* SMTP UTILITY COMMANDS */
1343 /*****************************************************************************/
1345 void cmd_smtp(char *argbuf) {
1352 if (CtdlAccessCheck(ac_aide)) return;
1354 extract(cmd, argbuf, 0);
1356 if (!strcasecmp(cmd, "mx")) {
1357 extract(node, argbuf, 1);
1358 num_mxhosts = getmx(buf, node);
1359 cprintf("%d %d MX hosts listed for %s\n",
1360 LISTING_FOLLOWS, num_mxhosts, node);
1361 for (i=0; i<num_mxhosts; ++i) {
1362 extract(node, buf, i);
1363 cprintf("%s\n", node);
1369 else if (!strcasecmp(cmd, "runqueue")) {
1371 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1376 cprintf("%d Invalid command.\n", ERROR+ILLEGAL_VALUE);
1383 * Initialize the SMTP outbound queue
1385 void smtp_init_spoolout(void) {
1386 struct quickroom qrbuf;
1389 * Create the room. This will silently fail if the room already
1390 * exists, and that's perfectly ok, because we want it to exist.
1392 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0);
1395 * Make sure it's set to be a "system room" so it doesn't show up
1396 * in the <K>nown rooms list for Aides.
1398 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1399 qrbuf.QRflags2 |= QR2_SYSTEM;
1407 /*****************************************************************************/
1408 /* MODULE INITIALIZATION STUFF */
1409 /*****************************************************************************/
1412 char *serv_smtp_init(void)
1414 SYM_SMTP = CtdlGetDynamicSymbol();
1416 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1421 CtdlRegisterServiceHook(0, /* ...and locally */
1426 smtp_init_spoolout();
1427 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1428 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");