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 user 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");
140 cprintf("250 ENHANCEDSTATUSCODES\r\n");
146 * Implement HELP command.
148 void smtp_help(void) {
149 cprintf("214-Commands accepted:\r\n");
150 cprintf("214- DATA\r\n");
151 cprintf("214- EHLO\r\n");
152 cprintf("214- EXPN\r\n");
153 cprintf("214- HELO\r\n");
154 cprintf("214- HELP\r\n");
155 cprintf("214- MAIL\r\n");
156 cprintf("214- NOOP\r\n");
157 cprintf("214- QUIT\r\n");
158 cprintf("214- RCPT\r\n");
159 cprintf("214- RSET\r\n");
160 cprintf("214- VRFY\r\n");
168 void smtp_get_user(char *argbuf) {
172 CtdlDecodeBase64(username, argbuf, SIZ);
173 lprintf(9, "Trying <%s>\n", username);
174 if (CtdlLoginExistingUser(username) == login_ok) {
175 CtdlEncodeBase64(buf, "Password:", 9);
176 cprintf("334 %s\r\n", buf);
177 SMTP->command_state = smtp_password;
180 cprintf("500 5.7.0 No such user.\r\n");
181 SMTP->command_state = smtp_command;
189 void smtp_get_pass(char *argbuf) {
192 CtdlDecodeBase64(password, argbuf, SIZ);
193 lprintf(9, "Trying <%s>\n", password);
194 if (CtdlTryPassword(password) == pass_ok) {
195 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
196 lprintf(9, "SMTP authenticated login successful\n");
197 CC->internal_pgm = 0;
198 CC->cs_flags &= ~CS_STEALTH;
201 cprintf("500 5.7.0 Authentication failed.\r\n");
203 SMTP->command_state = smtp_command;
210 void smtp_auth(char *argbuf) {
213 if (strncasecmp(argbuf, "login", 5) ) {
214 cprintf("550 5.7.4 We only support LOGIN authentication.\r\n");
218 if (strlen(argbuf) >= 7) {
219 smtp_get_user(&argbuf[6]);
223 CtdlEncodeBase64(buf, "Username:", 9);
224 cprintf("334 %s\r\n", buf);
225 SMTP->command_state = smtp_user;
231 * Back end for smtp_vrfy() command
233 void smtp_vrfy_backend(struct user *us, void *data) {
235 if (!fuzzy_match(us, SMTP->vrfy_match)) {
237 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct user));
243 * Implements the VRFY (verify user name) command.
244 * Performs fuzzy match on full user names.
246 void smtp_vrfy(char *argbuf) {
247 SMTP->vrfy_count = 0;
248 strcpy(SMTP->vrfy_match, argbuf);
249 ForEachUser(smtp_vrfy_backend, NULL);
251 if (SMTP->vrfy_count < 1) {
252 cprintf("550 5.1.1 String does not match anything.\r\n");
254 else if (SMTP->vrfy_count == 1) {
255 cprintf("250 %s <cit%ld@%s>\r\n",
256 SMTP->vrfy_buffer.fullname,
257 SMTP->vrfy_buffer.usernum,
260 else if (SMTP->vrfy_count > 1) {
261 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
270 * Back end for smtp_expn() command
272 void smtp_expn_backend(struct user *us, void *data) {
274 if (!fuzzy_match(us, SMTP->vrfy_match)) {
276 if (SMTP->vrfy_count >= 1) {
277 cprintf("250-%s <cit%ld@%s>\r\n",
278 SMTP->vrfy_buffer.fullname,
279 SMTP->vrfy_buffer.usernum,
284 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct user));
290 * Implements the EXPN (expand user name) command.
291 * Performs fuzzy match on full user names.
293 void smtp_expn(char *argbuf) {
294 SMTP->vrfy_count = 0;
295 strcpy(SMTP->vrfy_match, argbuf);
296 ForEachUser(smtp_expn_backend, NULL);
298 if (SMTP->vrfy_count < 1) {
299 cprintf("550 5.1.1 String does not match anything.\r\n");
301 else if (SMTP->vrfy_count >= 1) {
302 cprintf("250 %s <cit%ld@%s>\r\n",
303 SMTP->vrfy_buffer.fullname,
304 SMTP->vrfy_buffer.usernum,
311 * Implements the RSET (reset state) command.
312 * Currently this just zeroes out the state buffer. If pointers to data
313 * allocated with mallok() are ever placed in the state buffer, we have to
314 * be sure to phree() them first!
316 void smtp_rset(void) {
317 memset(SMTP, 0, sizeof(struct citsmtp));
320 * It is somewhat ambiguous whether we want to log out when a RSET
321 * command is issued. Here's the code to do it. It is commented out
322 * because some clients (such as Pine) issue RSET commands before
323 * each message, but still expect to be logged in.
325 * if (CC->logged_in) {
330 cprintf("250 2.0.0 Zap!\r\n");
334 * Clear out the portions of the state buffer that need to be cleared out
335 * after the DATA command finishes.
337 void smtp_data_clear(void) {
338 strcpy(SMTP->from, "");
339 strcpy(SMTP->recipients, "");
340 SMTP->number_of_recipients = 0;
341 SMTP->delivery_mode = 0;
342 SMTP->message_originated_locally = 0;
348 * Implements the "MAIL From:" command
350 void smtp_mail(char *argbuf) {
355 if (strlen(SMTP->from) != 0) {
356 cprintf("503 5.1.0 Only one sender permitted\r\n");
360 if (strncasecmp(argbuf, "From:", 5)) {
361 cprintf("501 5.1.7 Syntax error\r\n");
365 strcpy(SMTP->from, &argbuf[5]);
367 stripallbut(SMTP->from, '<', '>');
369 /* We used to reject empty sender names, until it was brought to our
370 * attention that RFC1123 5.2.9 requires that this be allowed. So now
371 * we allow it, but replace the empty string with a fake
372 * address so we don't have to contend with the empty string causing
373 * other code to fail when it's expecting something there.
375 if (strlen(SMTP->from) == 0) {
376 strcpy(SMTP->from, "someone@somewhere.org");
379 /* If this SMTP connection is from a logged-in user, force the 'from'
380 * to be the user's Internet e-mail address as Citadel knows it.
383 strcpy(SMTP->from, CC->cs_inet_email);
384 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
385 SMTP->message_originated_locally = 1;
389 /* Otherwise, make sure outsiders aren't trying to forge mail from
393 process_rfc822_addr(SMTP->from, user, node, name);
394 if (CtdlHostAlias(node) != hostalias_nomatch) {
396 "You must log in to send mail from %s\r\n",
398 strcpy(SMTP->from, "");
403 cprintf("250 2.0.0 Sender ok\r\n");
409 * Implements the "RCPT To:" command
411 void smtp_rcpt(char *argbuf) {
413 char message_to_spammer[SIZ];
414 struct recptypes *valid = NULL;
416 if (strlen(SMTP->from) == 0) {
417 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
421 if (strncasecmp(argbuf, "To:", 3)) {
422 cprintf("501 5.1.7 Syntax error\r\n");
426 strcpy(recp, &argbuf[3]);
428 stripallbut(recp, '<', '>');
430 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
431 cprintf("452 4.5.3 Too many recipients\r\n");
436 if ( (!CC->logged_in) && (!CC->is_local_socket) ) {
437 if (rbl_check(message_to_spammer)) {
438 cprintf("550 %s\r\n", message_to_spammer);
439 /* no need to phree(valid), it's not allocated yet */
444 valid = validate_recipients(recp);
445 if (valid->num_error > 0) {
446 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
451 if (valid->num_internet > 0) {
452 if (SMTP->message_originated_locally == 0) {
453 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
459 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
460 if (strlen(SMTP->recipients) > 0) {
461 strcat(SMTP->recipients, ",");
463 strcat(SMTP->recipients, recp);
464 SMTP->number_of_recipients += 1;
471 * Implements the DATA command
473 void smtp_data(void) {
475 struct CtdlMessage *msg;
478 struct recptypes *valid;
481 if (strlen(SMTP->from) == 0) {
482 cprintf("503 5.5.1 Need MAIL command first.\r\n");
486 if (SMTP->number_of_recipients < 1) {
487 cprintf("503 5.5.1 Need RCPT command first.\r\n");
491 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
493 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
497 it should be Received: from %s (real.name.dom [w.x.y.z])
499 if (body != NULL) snprintf(body, 4096,
500 "Received: from %s (%s)\n"
507 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
510 "Unable to save message: internal error.\r\n");
514 lprintf(9, "Converting message...\n");
515 msg = convert_internet_message(body);
517 /* If the user is locally authenticated, FORCE the From: header to
518 * show up as the real sender. Yes, this violates the RFC standard,
519 * but IT MAKES SENSE. If you prefer strict RFC adherence over
520 * common sense, you can disable this in the configuration.
522 * We also set the "message room name" ('O' field) to MAILROOM
523 * (which is Mail> on most systems) to prevent it from getting set
524 * to something ugly like "0000058008.Sent Items>" when the message
525 * is read with a Citadel client.
527 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
528 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
529 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
530 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
531 if (msg->cm_fields['F'] != NULL) phree(msg->cm_fields['F']);
532 if (msg->cm_fields['O'] != NULL) phree(msg->cm_fields['O']);
533 msg->cm_fields['A'] = strdoop(CC->user.fullname);
534 msg->cm_fields['N'] = strdoop(config.c_nodename);
535 msg->cm_fields['H'] = strdoop(config.c_humannode);
536 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
537 msg->cm_fields['O'] = strdoop(MAILROOM);
540 /* Submit the message into the Citadel system. */
541 valid = validate_recipients(SMTP->recipients);
543 /* If there are modules that want to scan this message before final
544 * submission (such as virus checkers or spam filters), call them now
545 * and give them an opportunity to reject the message.
547 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
549 if (scan_errors > 0) { /* We don't want this message! */
551 if (msg->cm_fields['0'] == NULL) {
552 msg->cm_fields['0'] = strdoop(
553 "5.7.1 Message rejected by filter");
556 cprintf("550 %s\r\n", msg->cm_fields['0']);
559 else { /* Ok, we'll accept this message. */
560 msgnum = CtdlSubmitMsg(msg, valid, "");
562 cprintf("250 2.0.0 Message accepted.\r\n");
565 cprintf("550 5.5.0 Internal delivery error\r\n");
569 CtdlFreeMessage(msg);
571 smtp_data_clear(); /* clear out the buffers now */
578 * Main command loop for SMTP sessions.
580 void smtp_command_loop(void) {
584 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
585 if (client_gets(cmdbuf) < 1) {
586 lprintf(3, "SMTP socket is broken. Ending session.\n");
590 lprintf(5, "SMTP: %s\n", cmdbuf);
591 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
593 lprintf(9, "CC->logged_in = %d\n", CC->logged_in);
595 if (SMTP->command_state == smtp_user) {
596 smtp_get_user(cmdbuf);
599 else if (SMTP->command_state == smtp_password) {
600 smtp_get_pass(cmdbuf);
603 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
604 smtp_auth(&cmdbuf[5]);
607 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
611 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
612 smtp_hello(&cmdbuf[5], 1);
615 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
616 smtp_expn(&cmdbuf[5]);
619 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
620 smtp_hello(&cmdbuf[5], 0);
623 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
627 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
628 smtp_mail(&cmdbuf[5]);
631 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
632 cprintf("250 NOOP\r\n");
635 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
636 cprintf("221 Goodbye...\r\n");
641 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
642 smtp_rcpt(&cmdbuf[5]);
645 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
649 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
650 smtp_vrfy(&cmdbuf[5]);
654 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
662 /*****************************************************************************/
663 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
664 /*****************************************************************************/
671 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
674 void smtp_try(const char *key, const char *addr, int *status,
675 char *dsn, size_t n, long msgnum)
682 char user[SIZ], node[SIZ], name[SIZ];
688 size_t blocksize = 0;
691 /* Parse out the host portion of the recipient address */
692 process_rfc822_addr(addr, user, node, name);
694 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
697 /* Load the message out of the database into a temp file */
699 if (msg_fp == NULL) {
701 snprintf(dsn, n, "Error creating temporary file");
705 CtdlRedirectOutput(msg_fp, -1);
706 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
707 CtdlRedirectOutput(NULL, -1);
708 fseek(msg_fp, 0L, SEEK_END);
709 msg_size = ftell(msg_fp);
713 /* Extract something to send later in the 'MAIL From:' command */
714 strcpy(mailfrom, "");
718 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
719 if (!strncasecmp(buf, "From:", 5)) {
720 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
722 for (i=0; i<strlen(mailfrom); ++i) {
723 if (!isprint(mailfrom[i])) {
724 strcpy(&mailfrom[i], &mailfrom[i+1]);
729 /* Strip out parenthesized names */
732 for (i=0; i<strlen(mailfrom); ++i) {
733 if (mailfrom[i] == '(') lp = i;
734 if (mailfrom[i] == ')') rp = i;
736 if ((lp>0)&&(rp>lp)) {
737 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
740 /* Prefer brokketized names */
743 for (i=0; i<strlen(mailfrom); ++i) {
744 if (mailfrom[i] == '<') lp = i;
745 if (mailfrom[i] == '>') rp = i;
747 if ( (lp>=0) && (rp>lp) ) {
749 strcpy(mailfrom, &mailfrom[lp]);
754 } while (scan_done == 0);
755 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
757 /* Figure out what mail exchanger host we have to connect to */
758 num_mxhosts = getmx(mxhosts, node);
759 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
760 if (num_mxhosts < 1) {
762 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
767 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
768 extract(buf, mxhosts, mx);
769 lprintf(9, "Trying <%s>\n", buf);
770 sock = sock_connect(buf, "25", "tcp");
771 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
772 if (sock >= 0) lprintf(9, "Connected!\n");
773 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
777 *status = 4; /* dsn is already filled in */
781 /* Process the SMTP greeting from the server */
782 if (ml_sock_gets(sock, buf) < 0) {
784 strcpy(dsn, "Connection broken during SMTP conversation");
787 lprintf(9, "<%s\n", buf);
791 safestrncpy(dsn, &buf[4], 1023);
796 safestrncpy(dsn, &buf[4], 1023);
801 /* At this point we know we are talking to a real SMTP server */
803 /* Do a HELO command */
804 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
805 lprintf(9, ">%s", buf);
806 sock_write(sock, buf, strlen(buf));
807 if (ml_sock_gets(sock, buf) < 0) {
809 strcpy(dsn, "Connection broken during SMTP HELO");
812 lprintf(9, "<%s\n", buf);
816 safestrncpy(dsn, &buf[4], 1023);
821 safestrncpy(dsn, &buf[4], 1023);
827 /* HELO succeeded, now try the MAIL From: command */
828 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
829 lprintf(9, ">%s", buf);
830 sock_write(sock, buf, strlen(buf));
831 if (ml_sock_gets(sock, buf) < 0) {
833 strcpy(dsn, "Connection broken during SMTP MAIL");
836 lprintf(9, "<%s\n", buf);
840 safestrncpy(dsn, &buf[4], 1023);
845 safestrncpy(dsn, &buf[4], 1023);
851 /* MAIL succeeded, now try the RCPT To: command */
852 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
853 lprintf(9, ">%s", buf);
854 sock_write(sock, buf, strlen(buf));
855 if (ml_sock_gets(sock, buf) < 0) {
857 strcpy(dsn, "Connection broken during SMTP RCPT");
860 lprintf(9, "<%s\n", buf);
864 safestrncpy(dsn, &buf[4], 1023);
869 safestrncpy(dsn, &buf[4], 1023);
875 /* RCPT succeeded, now try the DATA command */
876 lprintf(9, ">DATA\n");
877 sock_write(sock, "DATA\r\n", 6);
878 if (ml_sock_gets(sock, buf) < 0) {
880 strcpy(dsn, "Connection broken during SMTP DATA");
883 lprintf(9, "<%s\n", buf);
887 safestrncpy(dsn, &buf[4], 1023);
892 safestrncpy(dsn, &buf[4], 1023);
897 /* If we reach this point, the server is expecting data */
899 while (msg_size > 0) {
900 blocksize = sizeof(buf);
901 if (blocksize > msg_size) blocksize = msg_size;
902 fread(buf, blocksize, 1, msg_fp);
903 sock_write(sock, buf, blocksize);
904 msg_size -= blocksize;
906 if (buf[blocksize-1] != 10) {
907 lprintf(5, "Possible problem: message did not correctly "
908 "terminate. (expecting 0x10, got 0x%02x)\n",
912 sock_write(sock, ".\r\n", 3);
913 if (ml_sock_gets(sock, buf) < 0) {
915 strcpy(dsn, "Connection broken during SMTP message transmit");
918 lprintf(9, "%s\n", buf);
922 safestrncpy(dsn, &buf[4], 1023);
927 safestrncpy(dsn, &buf[4], 1023);
933 safestrncpy(dsn, &buf[4], 1023);
936 lprintf(9, ">QUIT\n");
937 sock_write(sock, "QUIT\r\n", 6);
938 ml_sock_gets(sock, buf);
939 lprintf(9, "<%s\n", buf);
941 bail: if (msg_fp != NULL) fclose(msg_fp);
949 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
950 * instructions for "5" codes (permanent fatal errors) and produce/deliver
951 * a "bounce" message (delivery status notification).
953 void smtp_do_bounce(char *instr) {
964 long bounce_msgid = (-1);
965 time_t submitted = 0L;
966 struct CtdlMessage *bmsg = NULL;
968 struct recptypes *valid;
969 int successful_bounce = 0;
971 lprintf(9, "smtp_do_bounce() called\n");
972 strcpy(bounceto, "");
974 lines = num_tokens(instr, '\n');
977 /* See if it's time to give up on delivery of this message */
978 for (i=0; i<lines; ++i) {
979 extract_token(buf, instr, i, '\n');
980 extract(key, buf, 0);
981 extract(addr, buf, 1);
982 if (!strcasecmp(key, "submitted")) {
983 submitted = atol(addr);
987 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
993 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
994 if (bmsg == NULL) return;
995 memset(bmsg, 0, sizeof(struct CtdlMessage));
997 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
998 bmsg->cm_anon_type = MES_NORMAL;
999 bmsg->cm_format_type = 1;
1000 bmsg->cm_fields['A'] = strdoop("Citadel");
1001 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1002 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1004 if (give_up) bmsg->cm_fields['M'] = strdoop(
1005 "A message you sent could not be delivered to some or all of its recipients\n"
1006 "due to prolonged unavailability of its destination(s).\n"
1007 "Giving up on the following addresses:\n\n"
1010 else bmsg->cm_fields['M'] = strdoop(
1011 "A message you sent could not be delivered to some or all of its recipients.\n"
1012 "The following addresses were undeliverable:\n\n"
1016 * Now go through the instructions checking for stuff.
1018 for (i=0; i<lines; ++i) {
1019 extract_token(buf, instr, i, '\n');
1020 extract(key, buf, 0);
1021 extract(addr, buf, 1);
1022 status = extract_int(buf, 2);
1023 extract(dsn, buf, 3);
1026 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1027 key, addr, status, dsn);
1029 if (!strcasecmp(key, "bounceto")) {
1030 strcpy(bounceto, addr);
1034 (!strcasecmp(key, "local"))
1035 || (!strcasecmp(key, "remote"))
1036 || (!strcasecmp(key, "ignet"))
1037 || (!strcasecmp(key, "room"))
1039 if (status == 5) bounce_this = 1;
1040 if (give_up) bounce_this = 1;
1046 if (bmsg->cm_fields['M'] == NULL) {
1047 lprintf(2, "ERROR ... M field is null "
1048 "(%s:%d)\n", __FILE__, __LINE__);
1051 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1052 strlen(bmsg->cm_fields['M']) + 1024 );
1053 strcat(bmsg->cm_fields['M'], addr);
1054 strcat(bmsg->cm_fields['M'], ": ");
1055 strcat(bmsg->cm_fields['M'], dsn);
1056 strcat(bmsg->cm_fields['M'], "\n");
1058 remove_token(instr, i, '\n');
1064 /* Deliver the bounce if there's anything worth mentioning */
1065 lprintf(9, "num_bounces = %d\n", num_bounces);
1066 if (num_bounces > 0) {
1068 /* First try the user who sent the message */
1069 lprintf(9, "bounce to user? <%s>\n", bounceto);
1070 if (strlen(bounceto) == 0) {
1071 lprintf(7, "No bounce address specified\n");
1072 bounce_msgid = (-1L);
1075 /* Can we deliver the bounce to the original sender? */
1076 valid = validate_recipients(bounceto);
1077 if (valid != NULL) {
1078 if (valid->num_error == 0) {
1079 CtdlSubmitMsg(bmsg, valid, "");
1080 successful_bounce = 1;
1084 /* If not, post it in the Aide> room */
1085 if (successful_bounce == 0) {
1086 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1089 /* Free up the memory we used */
1090 if (valid != NULL) {
1095 CtdlFreeMessage(bmsg);
1096 lprintf(9, "Done processing bounces\n");
1101 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1102 * set of delivery instructions for completed deliveries and remove them.
1104 * It returns the number of incomplete deliveries remaining.
1106 int smtp_purge_completed_deliveries(char *instr) {
1117 lines = num_tokens(instr, '\n');
1118 for (i=0; i<lines; ++i) {
1119 extract_token(buf, instr, i, '\n');
1120 extract(key, buf, 0);
1121 extract(addr, buf, 1);
1122 status = extract_int(buf, 2);
1123 extract(dsn, buf, 3);
1128 (!strcasecmp(key, "local"))
1129 || (!strcasecmp(key, "remote"))
1130 || (!strcasecmp(key, "ignet"))
1131 || (!strcasecmp(key, "room"))
1133 if (status == 2) completed = 1;
1138 remove_token(instr, i, '\n');
1151 * Called by smtp_do_queue() to handle an individual message.
1153 void smtp_do_procmsg(long msgnum, void *userdata) {
1154 struct CtdlMessage *msg;
1156 char *results = NULL;
1164 long text_msgid = (-1);
1165 int incomplete_deliveries_remaining;
1166 time_t attempted = 0L;
1167 time_t last_attempted = 0L;
1168 time_t retry = SMTP_RETRY_INTERVAL;
1170 lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1172 msg = CtdlFetchMessage(msgnum);
1174 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1178 instr = strdoop(msg->cm_fields['M']);
1179 CtdlFreeMessage(msg);
1181 /* Strip out the headers amd any other non-instruction line */
1182 lines = num_tokens(instr, '\n');
1183 for (i=0; i<lines; ++i) {
1184 extract_token(buf, instr, i, '\n');
1185 if (num_tokens(buf, '|') < 2) {
1186 remove_token(instr, i, '\n');
1192 /* Learn the message ID and find out about recent delivery attempts */
1193 lines = num_tokens(instr, '\n');
1194 for (i=0; i<lines; ++i) {
1195 extract_token(buf, instr, i, '\n');
1196 extract(key, buf, 0);
1197 if (!strcasecmp(key, "msgid")) {
1198 text_msgid = extract_long(buf, 1);
1200 if (!strcasecmp(key, "retry")) {
1201 /* double the retry interval after each attempt */
1202 retry = extract_long(buf, 1) * 2L;
1203 if (retry > SMTP_RETRY_MAX) {
1204 retry = SMTP_RETRY_MAX;
1206 remove_token(instr, i, '\n');
1208 if (!strcasecmp(key, "attempted")) {
1209 attempted = extract_long(buf, 1);
1210 if (attempted > last_attempted)
1211 last_attempted = attempted;
1216 * Postpone delivery if we've already tried recently.
1218 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1219 lprintf(7, "Retry time not yet reached.\n");
1226 * Bail out if there's no actual message associated with this
1228 if (text_msgid < 0L) {
1229 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1234 /* Plow through the instructions looking for 'remote' directives and
1235 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1236 * were experienced and it's time to try again)
1238 lines = num_tokens(instr, '\n');
1239 for (i=0; i<lines; ++i) {
1240 extract_token(buf, instr, i, '\n');
1241 extract(key, buf, 0);
1242 extract(addr, buf, 1);
1243 status = extract_int(buf, 2);
1244 extract(dsn, buf, 3);
1245 if ( (!strcasecmp(key, "remote"))
1246 && ((status==0)||(status==3)||(status==4)) ) {
1248 /* Remove this "remote" instruction from the set,
1249 * but replace the set's final newline if
1250 * remove_token() stripped it. It has to be there.
1252 remove_token(instr, i, '\n');
1253 if (instr[strlen(instr)-1] != '\n') {
1254 strcat(instr, "\n");
1259 lprintf(9, "SMTP: Trying <%s>\n", addr);
1260 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1262 if (results == NULL) {
1263 results = mallok(1024);
1264 memset(results, 0, 1024);
1267 results = reallok(results,
1268 strlen(results) + 1024);
1270 snprintf(&results[strlen(results)], 1024,
1272 key, addr, status, dsn);
1277 if (results != NULL) {
1278 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1279 strcat(instr, results);
1284 /* Generate 'bounce' messages */
1285 smtp_do_bounce(instr);
1287 /* Go through the delivery list, deleting completed deliveries */
1288 incomplete_deliveries_remaining =
1289 smtp_purge_completed_deliveries(instr);
1293 * No delivery instructions remain, so delete both the instructions
1294 * message and the message message.
1296 if (incomplete_deliveries_remaining <= 0) {
1297 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1298 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1303 * Uncompleted delivery instructions remain, so delete the old
1304 * instructions and replace with the updated ones.
1306 if (incomplete_deliveries_remaining > 0) {
1307 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1308 msg = mallok(sizeof(struct CtdlMessage));
1309 memset(msg, 0, sizeof(struct CtdlMessage));
1310 msg->cm_magic = CTDLMESSAGE_MAGIC;
1311 msg->cm_anon_type = MES_NORMAL;
1312 msg->cm_format_type = FMT_RFC822;
1313 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1314 snprintf(msg->cm_fields['M'],
1316 "Content-type: %s\n\n%s\n"
1319 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1321 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1322 CtdlFreeMessage(msg);
1332 * Run through the queue sending out messages.
1334 void smtp_do_queue(void) {
1335 static int doing_queue = 0;
1338 * This is a simple concurrency check to make sure only one queue run
1339 * is done at a time. We could do this with a mutex, but since we
1340 * don't really require extremely fine granularity here, we'll do it
1341 * with a static variable instead.
1343 if (doing_queue) return;
1347 * Go ahead and run the queue
1349 lprintf(7, "SMTP: processing outbound queue\n");
1351 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1352 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1355 CtdlForEachMessage(MSGS_ALL, 0L,
1356 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1358 lprintf(7, "SMTP: queue run completed\n");
1365 /*****************************************************************************/
1366 /* SMTP UTILITY COMMANDS */
1367 /*****************************************************************************/
1369 void cmd_smtp(char *argbuf) {
1376 if (CtdlAccessCheck(ac_aide)) return;
1378 extract(cmd, argbuf, 0);
1380 if (!strcasecmp(cmd, "mx")) {
1381 extract(node, argbuf, 1);
1382 num_mxhosts = getmx(buf, node);
1383 cprintf("%d %d MX hosts listed for %s\n",
1384 LISTING_FOLLOWS, num_mxhosts, node);
1385 for (i=0; i<num_mxhosts; ++i) {
1386 extract(node, buf, i);
1387 cprintf("%s\n", node);
1393 else if (!strcasecmp(cmd, "runqueue")) {
1395 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1400 cprintf("%d Invalid command.\n", ERROR+ILLEGAL_VALUE);
1407 * Initialize the SMTP outbound queue
1409 void smtp_init_spoolout(void) {
1413 * Create the room. This will silently fail if the room already
1414 * exists, and that's perfectly ok, because we want it to exist.
1416 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0);
1419 * Make sure it's set to be a "system room" so it doesn't show up
1420 * in the <K>nown rooms list for Aides.
1422 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1423 qrbuf.QRflags2 |= QR2_SYSTEM;
1431 /*****************************************************************************/
1432 /* MODULE INITIALIZATION STUFF */
1433 /*****************************************************************************/
1436 char *serv_smtp_init(void)
1438 SYM_SMTP = CtdlGetDynamicSymbol();
1440 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1445 CtdlRegisterServiceHook(0, /* ...and locally */
1450 smtp_init_spoolout();
1451 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1452 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");