4 * This module implements the following protocols for the Citadel system:
6 * RFC 821 (Simple Mail Transfer Protocol)
7 * RFC 1854 (command pipelining)
8 * RFC 1869 (Extended Simple Mail Transfer Protocol)
9 * RFC 2033 (Local Mail Transfer Protocol)
10 * RFC 2034 (enhanced status codes)
22 #include <sys/types.h>
24 #if TIME_WITH_SYS_TIME
25 # include <sys/time.h>
29 # include <sys/time.h>
39 #include <sys/socket.h>
40 #include <netinet/in.h>
41 #include <arpa/inet.h>
44 #include "sysdep_decls.h"
45 #include "citserver.h"
49 #include "serv_extensions.h"
56 #include "internet_addressing.h"
59 #include "clientsocket.h"
60 #include "locate_host.h"
67 struct citsmtp { /* Information about the current session */
70 struct ctdluser vrfy_buffer;
75 int number_of_recipients;
77 int message_originated_locally;
81 enum { /* Command states for login authentication */
87 enum { /* Delivery modes */
92 #define SMTP ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
93 #define SMTP_RECPS ((char *)CtdlGetUserData(SYM_SMTP_RECPS))
94 #define SMTP_ROOMS ((char *)CtdlGetUserData(SYM_SMTP_ROOMS))
97 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
101 /*****************************************************************************/
102 /* SMTP SERVER (INBOUND) STUFF */
103 /*****************************************************************************/
109 * Here's where our SMTP session begins its happy day.
111 void smtp_greeting(void) {
113 strcpy(CC->cs_clientname, "SMTP session");
114 CC->internal_pgm = 1;
115 CC->cs_flags |= CS_STEALTH;
116 CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
117 CtdlAllocUserData(SYM_SMTP_RECPS, SIZ);
118 CtdlAllocUserData(SYM_SMTP_ROOMS, SIZ);
119 snprintf(SMTP_RECPS, SIZ, "%s", "");
120 snprintf(SMTP_ROOMS, SIZ, "%s", "");
122 cprintf("220 %s ESMTP Citadel/UX server ready.\r\n", config.c_fqdn);
126 * LMTP is like SMTP but with some extra bonus footage added.
128 void lmtp_greeting(void) {
135 * Implement HELO and EHLO commands.
137 * which_command: 0=HELO, 1=EHLO, 2=LHLO
139 void smtp_hello(char *argbuf, int which_command) {
141 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
143 if ( (which_command != 2) && (SMTP->is_lmtp) ) {
144 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
148 if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
149 cprintf("500 LHLO is only allowed when running LMTP\r\n");
153 if (which_command == 0) {
154 cprintf("250 Greetings and joyous salutations.\r\n");
157 cprintf("250-Greetings and joyous salutations.\r\n");
158 cprintf("250-HELP\r\n");
159 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
160 cprintf("250-PIPELINING\r\n");
161 cprintf("250-AUTH=LOGIN\r\n");
162 cprintf("250 ENHANCEDSTATUSCODES\r\n");
168 * Implement HELP command.
170 void smtp_help(void) {
171 cprintf("214-Commands accepted:\r\n");
172 cprintf("214- DATA\r\n");
173 cprintf("214- EHLO\r\n");
174 cprintf("214- EXPN\r\n");
175 cprintf("214- HELO\r\n");
176 cprintf("214- HELP\r\n");
177 cprintf("214- MAIL\r\n");
178 cprintf("214- NOOP\r\n");
179 cprintf("214- QUIT\r\n");
180 cprintf("214- RCPT\r\n");
181 cprintf("214- RSET\r\n");
182 cprintf("214- VRFY\r\n");
190 void smtp_get_user(char *argbuf) {
194 CtdlDecodeBase64(username, argbuf, SIZ);
195 lprintf(9, "Trying <%s>\n", username);
196 if (CtdlLoginExistingUser(username) == login_ok) {
197 CtdlEncodeBase64(buf, "Password:", 9);
198 cprintf("334 %s\r\n", buf);
199 SMTP->command_state = smtp_password;
202 cprintf("500 5.7.0 No such user.\r\n");
203 SMTP->command_state = smtp_command;
211 void smtp_get_pass(char *argbuf) {
214 CtdlDecodeBase64(password, argbuf, SIZ);
215 lprintf(9, "Trying <%s>\n", password);
216 if (CtdlTryPassword(password) == pass_ok) {
217 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
218 lprintf(9, "SMTP authenticated login successful\n");
219 CC->internal_pgm = 0;
220 CC->cs_flags &= ~CS_STEALTH;
223 cprintf("500 5.7.0 Authentication failed.\r\n");
225 SMTP->command_state = smtp_command;
232 void smtp_auth(char *argbuf) {
235 if (strncasecmp(argbuf, "login", 5) ) {
236 cprintf("550 5.7.4 We only support LOGIN authentication.\r\n");
240 if (strlen(argbuf) >= 7) {
241 smtp_get_user(&argbuf[6]);
245 CtdlEncodeBase64(buf, "Username:", 9);
246 cprintf("334 %s\r\n", buf);
247 SMTP->command_state = smtp_user;
253 * Back end for smtp_vrfy() command
255 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
257 if (!fuzzy_match(us, SMTP->vrfy_match)) {
259 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
265 * Implements the VRFY (verify user name) command.
266 * Performs fuzzy match on full user names.
268 void smtp_vrfy(char *argbuf) {
269 SMTP->vrfy_count = 0;
270 strcpy(SMTP->vrfy_match, argbuf);
271 ForEachUser(smtp_vrfy_backend, NULL);
273 if (SMTP->vrfy_count < 1) {
274 cprintf("550 5.1.1 String does not match anything.\r\n");
276 else if (SMTP->vrfy_count == 1) {
277 cprintf("250 %s <cit%ld@%s>\r\n",
278 SMTP->vrfy_buffer.fullname,
279 SMTP->vrfy_buffer.usernum,
282 else if (SMTP->vrfy_count > 1) {
283 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
292 * Back end for smtp_expn() command
294 void smtp_expn_backend(struct ctdluser *us, void *data) {
296 if (!fuzzy_match(us, SMTP->vrfy_match)) {
298 if (SMTP->vrfy_count >= 1) {
299 cprintf("250-%s <cit%ld@%s>\r\n",
300 SMTP->vrfy_buffer.fullname,
301 SMTP->vrfy_buffer.usernum,
306 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
312 * Implements the EXPN (expand user name) command.
313 * Performs fuzzy match on full user names.
315 void smtp_expn(char *argbuf) {
316 SMTP->vrfy_count = 0;
317 strcpy(SMTP->vrfy_match, argbuf);
318 ForEachUser(smtp_expn_backend, NULL);
320 if (SMTP->vrfy_count < 1) {
321 cprintf("550 5.1.1 String does not match anything.\r\n");
323 else if (SMTP->vrfy_count >= 1) {
324 cprintf("250 %s <cit%ld@%s>\r\n",
325 SMTP->vrfy_buffer.fullname,
326 SMTP->vrfy_buffer.usernum,
333 * Implements the RSET (reset state) command.
334 * Currently this just zeroes out the state buffer. If pointers to data
335 * allocated with mallok() are ever placed in the state buffer, we have to
336 * be sure to phree() them first!
338 void smtp_rset(void) {
342 * Our entire SMTP state is discarded when a RSET command is issued,
343 * but we need to preserve this one little piece of information, so
344 * we save it for later.
346 is_lmtp = SMTP->is_lmtp;
348 memset(SMTP, 0, sizeof(struct citsmtp));
351 * It is somewhat ambiguous whether we want to log out when a RSET
352 * command is issued. Here's the code to do it. It is commented out
353 * because some clients (such as Pine) issue RSET commands before
354 * each message, but still expect to be logged in.
356 * if (CC->logged_in) {
362 * Reinstate this little piece of information we saved (see above).
364 SMTP->is_lmtp = is_lmtp;
366 cprintf("250 2.0.0 Zap!\r\n");
370 * Clear out the portions of the state buffer that need to be cleared out
371 * after the DATA command finishes.
373 void smtp_data_clear(void) {
374 strcpy(SMTP->from, "");
375 strcpy(SMTP->recipients, "");
376 SMTP->number_of_recipients = 0;
377 SMTP->delivery_mode = 0;
378 SMTP->message_originated_locally = 0;
384 * Implements the "MAIL From:" command
386 void smtp_mail(char *argbuf) {
391 if (strlen(SMTP->from) != 0) {
392 cprintf("503 5.1.0 Only one sender permitted\r\n");
396 if (strncasecmp(argbuf, "From:", 5)) {
397 cprintf("501 5.1.7 Syntax error\r\n");
401 strcpy(SMTP->from, &argbuf[5]);
403 stripallbut(SMTP->from, '<', '>');
405 /* We used to reject empty sender names, until it was brought to our
406 * attention that RFC1123 5.2.9 requires that this be allowed. So now
407 * we allow it, but replace the empty string with a fake
408 * address so we don't have to contend with the empty string causing
409 * other code to fail when it's expecting something there.
411 if (strlen(SMTP->from) == 0) {
412 strcpy(SMTP->from, "someone@somewhere.org");
415 /* If this SMTP connection is from a logged-in user, force the 'from'
416 * to be the user's Internet e-mail address as Citadel knows it.
419 strcpy(SMTP->from, CC->cs_inet_email);
420 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
421 SMTP->message_originated_locally = 1;
425 else if (SMTP->is_lmtp) {
426 /* Bypass forgery checking for LMTP */
429 /* Otherwise, make sure outsiders aren't trying to forge mail from
433 process_rfc822_addr(SMTP->from, user, node, name);
434 if (CtdlHostAlias(node) != hostalias_nomatch) {
436 "You must log in to send mail from %s\r\n",
438 strcpy(SMTP->from, "");
443 cprintf("250 2.0.0 Sender ok\r\n");
449 * Implements the "RCPT To:" command
451 void smtp_rcpt(char *argbuf) {
453 char message_to_spammer[SIZ];
454 struct recptypes *valid = NULL;
456 if (strlen(SMTP->from) == 0) {
457 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
461 if (strncasecmp(argbuf, "To:", 3)) {
462 cprintf("501 5.1.7 Syntax error\r\n");
466 strcpy(recp, &argbuf[3]);
468 stripallbut(recp, '<', '>');
470 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
471 cprintf("452 4.5.3 Too many recipients\r\n");
476 if ( (!CC->logged_in)
477 && (!SMTP->is_lmtp) ) {
478 if (rbl_check(message_to_spammer)) {
479 cprintf("550 %s\r\n", message_to_spammer);
480 /* no need to phree(valid), it's not allocated yet */
485 valid = validate_recipients(recp);
486 if (valid->num_error > 0) {
487 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
492 if (valid->num_internet > 0) {
493 if ( (SMTP->message_originated_locally == 0)
494 && (SMTP->is_lmtp == 0) ) {
495 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
501 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
502 if (strlen(SMTP->recipients) > 0) {
503 strcat(SMTP->recipients, ",");
505 strcat(SMTP->recipients, recp);
506 SMTP->number_of_recipients += 1;
513 * Implements the DATA command
515 void smtp_data(void) {
517 struct CtdlMessage *msg;
520 struct recptypes *valid;
525 if (strlen(SMTP->from) == 0) {
526 cprintf("503 5.5.1 Need MAIL command first.\r\n");
530 if (SMTP->number_of_recipients < 1) {
531 cprintf("503 5.5.1 Need RCPT command first.\r\n");
535 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
537 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
540 if (body != NULL) snprintf(body, 4096,
541 "Received: from %s (%s [%s])\n"
549 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
552 "Unable to save message: internal error.\r\n");
556 lprintf(9, "Converting message...\n");
557 msg = convert_internet_message(body);
559 /* If the user is locally authenticated, FORCE the From: header to
560 * show up as the real sender. Yes, this violates the RFC standard,
561 * but IT MAKES SENSE. If you prefer strict RFC adherence over
562 * common sense, you can disable this in the configuration.
564 * We also set the "message room name" ('O' field) to MAILROOM
565 * (which is Mail> on most systems) to prevent it from getting set
566 * to something ugly like "0000058008.Sent Items>" when the message
567 * is read with a Citadel client.
569 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
570 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
571 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
572 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
573 if (msg->cm_fields['F'] != NULL) phree(msg->cm_fields['F']);
574 if (msg->cm_fields['O'] != NULL) phree(msg->cm_fields['O']);
575 msg->cm_fields['A'] = strdoop(CC->user.fullname);
576 msg->cm_fields['N'] = strdoop(config.c_nodename);
577 msg->cm_fields['H'] = strdoop(config.c_humannode);
578 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
579 msg->cm_fields['O'] = strdoop(MAILROOM);
582 /* Submit the message into the Citadel system. */
583 valid = validate_recipients(SMTP->recipients);
585 /* If there are modules that want to scan this message before final
586 * submission (such as virus checkers or spam filters), call them now
587 * and give them an opportunity to reject the message.
589 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
591 if (scan_errors > 0) { /* We don't want this message! */
593 if (msg->cm_fields['0'] == NULL) {
594 msg->cm_fields['0'] = strdoop(
595 "5.7.1 Message rejected by filter");
598 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
601 else { /* Ok, we'll accept this message. */
602 msgnum = CtdlSubmitMsg(msg, valid, "");
604 sprintf(result, "250 2.0.0 Message accepted.\r\n");
607 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
611 /* For SMTP and ESTMP, just print the result message. For LMTP, we
612 * have to print one result message for each recipient. Since there
613 * is nothing in Citadel which would cause different recipients to
614 * have different results, we can get away with just spitting out the
615 * same message once for each recipient.
618 for (i=0; i<SMTP->number_of_recipients; ++i) {
619 cprintf("%s", result);
623 cprintf("%s", result);
626 CtdlFreeMessage(msg);
628 smtp_data_clear(); /* clear out the buffers now */
635 * Main command loop for SMTP sessions.
637 void smtp_command_loop(void) {
641 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
642 if (client_gets(cmdbuf) < 1) {
643 lprintf(3, "SMTP socket is broken. Ending session.\n");
647 lprintf(5, "SMTP: %s\n", cmdbuf);
648 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
650 lprintf(9, "CC->logged_in = %d\n", CC->logged_in);
652 if (SMTP->command_state == smtp_user) {
653 smtp_get_user(cmdbuf);
656 else if (SMTP->command_state == smtp_password) {
657 smtp_get_pass(cmdbuf);
660 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
661 smtp_auth(&cmdbuf[5]);
664 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
668 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
669 smtp_expn(&cmdbuf[5]);
672 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
673 smtp_hello(&cmdbuf[5], 0);
676 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
677 smtp_hello(&cmdbuf[5], 1);
680 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
681 smtp_hello(&cmdbuf[5], 2);
684 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
688 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
689 smtp_mail(&cmdbuf[5]);
692 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
693 cprintf("250 NOOP\r\n");
696 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
697 cprintf("221 Goodbye...\r\n");
702 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
703 smtp_rcpt(&cmdbuf[5]);
706 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
710 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
711 smtp_vrfy(&cmdbuf[5]);
715 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
723 /*****************************************************************************/
724 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
725 /*****************************************************************************/
732 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
735 void smtp_try(const char *key, const char *addr, int *status,
736 char *dsn, size_t n, long msgnum)
743 char user[SIZ], node[SIZ], name[SIZ];
749 size_t blocksize = 0;
752 /* Parse out the host portion of the recipient address */
753 process_rfc822_addr(addr, user, node, name);
755 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
758 /* Load the message out of the database into a temp file */
760 if (msg_fp == NULL) {
762 snprintf(dsn, n, "Error creating temporary file");
766 CtdlRedirectOutput(msg_fp, -1);
767 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
768 CtdlRedirectOutput(NULL, -1);
769 fseek(msg_fp, 0L, SEEK_END);
770 msg_size = ftell(msg_fp);
774 /* Extract something to send later in the 'MAIL From:' command */
775 strcpy(mailfrom, "");
779 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
780 if (!strncasecmp(buf, "From:", 5)) {
781 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
783 for (i=0; i<strlen(mailfrom); ++i) {
784 if (!isprint(mailfrom[i])) {
785 strcpy(&mailfrom[i], &mailfrom[i+1]);
790 /* Strip out parenthesized names */
793 for (i=0; i<strlen(mailfrom); ++i) {
794 if (mailfrom[i] == '(') lp = i;
795 if (mailfrom[i] == ')') rp = i;
797 if ((lp>0)&&(rp>lp)) {
798 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
801 /* Prefer brokketized names */
804 for (i=0; i<strlen(mailfrom); ++i) {
805 if (mailfrom[i] == '<') lp = i;
806 if (mailfrom[i] == '>') rp = i;
808 if ( (lp>=0) && (rp>lp) ) {
810 strcpy(mailfrom, &mailfrom[lp]);
815 } while (scan_done == 0);
816 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
818 /* Figure out what mail exchanger host we have to connect to */
819 num_mxhosts = getmx(mxhosts, node);
820 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
821 if (num_mxhosts < 1) {
823 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
828 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
829 extract(buf, mxhosts, mx);
830 lprintf(9, "Trying <%s>\n", buf);
831 sock = sock_connect(buf, "25", "tcp");
832 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
833 if (sock >= 0) lprintf(9, "Connected!\n");
834 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
838 *status = 4; /* dsn is already filled in */
842 /* Process the SMTP greeting from the server */
843 if (ml_sock_gets(sock, buf) < 0) {
845 strcpy(dsn, "Connection broken during SMTP conversation");
848 lprintf(9, "<%s\n", buf);
852 safestrncpy(dsn, &buf[4], 1023);
857 safestrncpy(dsn, &buf[4], 1023);
862 /* At this point we know we are talking to a real SMTP server */
864 /* Do a HELO command */
865 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
866 lprintf(9, ">%s", buf);
867 sock_write(sock, buf, strlen(buf));
868 if (ml_sock_gets(sock, buf) < 0) {
870 strcpy(dsn, "Connection broken during SMTP HELO");
873 lprintf(9, "<%s\n", buf);
877 safestrncpy(dsn, &buf[4], 1023);
882 safestrncpy(dsn, &buf[4], 1023);
888 /* HELO succeeded, now try the MAIL From: command */
889 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
890 lprintf(9, ">%s", buf);
891 sock_write(sock, buf, strlen(buf));
892 if (ml_sock_gets(sock, buf) < 0) {
894 strcpy(dsn, "Connection broken during SMTP MAIL");
897 lprintf(9, "<%s\n", buf);
901 safestrncpy(dsn, &buf[4], 1023);
906 safestrncpy(dsn, &buf[4], 1023);
912 /* MAIL succeeded, now try the RCPT To: command */
913 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
914 lprintf(9, ">%s", buf);
915 sock_write(sock, buf, strlen(buf));
916 if (ml_sock_gets(sock, buf) < 0) {
918 strcpy(dsn, "Connection broken during SMTP RCPT");
921 lprintf(9, "<%s\n", buf);
925 safestrncpy(dsn, &buf[4], 1023);
930 safestrncpy(dsn, &buf[4], 1023);
936 /* RCPT succeeded, now try the DATA command */
937 lprintf(9, ">DATA\n");
938 sock_write(sock, "DATA\r\n", 6);
939 if (ml_sock_gets(sock, buf) < 0) {
941 strcpy(dsn, "Connection broken during SMTP DATA");
944 lprintf(9, "<%s\n", buf);
948 safestrncpy(dsn, &buf[4], 1023);
953 safestrncpy(dsn, &buf[4], 1023);
958 /* If we reach this point, the server is expecting data */
960 while (msg_size > 0) {
961 blocksize = sizeof(buf);
962 if (blocksize > msg_size) blocksize = msg_size;
963 fread(buf, blocksize, 1, msg_fp);
964 sock_write(sock, buf, blocksize);
965 msg_size -= blocksize;
967 if (buf[blocksize-1] != 10) {
968 lprintf(5, "Possible problem: message did not correctly "
969 "terminate. (expecting 0x10, got 0x%02x)\n",
973 sock_write(sock, ".\r\n", 3);
974 if (ml_sock_gets(sock, buf) < 0) {
976 strcpy(dsn, "Connection broken during SMTP message transmit");
979 lprintf(9, "%s\n", buf);
983 safestrncpy(dsn, &buf[4], 1023);
988 safestrncpy(dsn, &buf[4], 1023);
994 safestrncpy(dsn, &buf[4], 1023);
997 lprintf(9, ">QUIT\n");
998 sock_write(sock, "QUIT\r\n", 6);
999 ml_sock_gets(sock, buf);
1000 lprintf(9, "<%s\n", buf);
1002 bail: if (msg_fp != NULL) fclose(msg_fp);
1010 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1011 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1012 * a "bounce" message (delivery status notification).
1014 void smtp_do_bounce(char *instr) {
1022 char bounceto[1024];
1023 int num_bounces = 0;
1024 int bounce_this = 0;
1025 long bounce_msgid = (-1);
1026 time_t submitted = 0L;
1027 struct CtdlMessage *bmsg = NULL;
1029 struct recptypes *valid;
1030 int successful_bounce = 0;
1032 lprintf(9, "smtp_do_bounce() called\n");
1033 strcpy(bounceto, "");
1035 lines = num_tokens(instr, '\n');
1038 /* See if it's time to give up on delivery of this message */
1039 for (i=0; i<lines; ++i) {
1040 extract_token(buf, instr, i, '\n');
1041 extract(key, buf, 0);
1042 extract(addr, buf, 1);
1043 if (!strcasecmp(key, "submitted")) {
1044 submitted = atol(addr);
1048 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1054 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1055 if (bmsg == NULL) return;
1056 memset(bmsg, 0, sizeof(struct CtdlMessage));
1058 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1059 bmsg->cm_anon_type = MES_NORMAL;
1060 bmsg->cm_format_type = 1;
1061 bmsg->cm_fields['A'] = strdoop("Citadel");
1062 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1063 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1065 if (give_up) bmsg->cm_fields['M'] = strdoop(
1066 "A message you sent could not be delivered to some or all of its recipients\n"
1067 "due to prolonged unavailability of its destination(s).\n"
1068 "Giving up on the following addresses:\n\n"
1071 else bmsg->cm_fields['M'] = strdoop(
1072 "A message you sent could not be delivered to some or all of its recipients.\n"
1073 "The following addresses were undeliverable:\n\n"
1077 * Now go through the instructions checking for stuff.
1079 for (i=0; i<lines; ++i) {
1080 extract_token(buf, instr, i, '\n');
1081 extract(key, buf, 0);
1082 extract(addr, buf, 1);
1083 status = extract_int(buf, 2);
1084 extract(dsn, buf, 3);
1087 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1088 key, addr, status, dsn);
1090 if (!strcasecmp(key, "bounceto")) {
1091 strcpy(bounceto, addr);
1095 (!strcasecmp(key, "local"))
1096 || (!strcasecmp(key, "remote"))
1097 || (!strcasecmp(key, "ignet"))
1098 || (!strcasecmp(key, "room"))
1100 if (status == 5) bounce_this = 1;
1101 if (give_up) bounce_this = 1;
1107 if (bmsg->cm_fields['M'] == NULL) {
1108 lprintf(2, "ERROR ... M field is null "
1109 "(%s:%d)\n", __FILE__, __LINE__);
1112 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1113 strlen(bmsg->cm_fields['M']) + 1024 );
1114 strcat(bmsg->cm_fields['M'], addr);
1115 strcat(bmsg->cm_fields['M'], ": ");
1116 strcat(bmsg->cm_fields['M'], dsn);
1117 strcat(bmsg->cm_fields['M'], "\n");
1119 remove_token(instr, i, '\n');
1125 /* Deliver the bounce if there's anything worth mentioning */
1126 lprintf(9, "num_bounces = %d\n", num_bounces);
1127 if (num_bounces > 0) {
1129 /* First try the user who sent the message */
1130 lprintf(9, "bounce to user? <%s>\n", bounceto);
1131 if (strlen(bounceto) == 0) {
1132 lprintf(7, "No bounce address specified\n");
1133 bounce_msgid = (-1L);
1136 /* Can we deliver the bounce to the original sender? */
1137 valid = validate_recipients(bounceto);
1138 if (valid != NULL) {
1139 if (valid->num_error == 0) {
1140 CtdlSubmitMsg(bmsg, valid, "");
1141 successful_bounce = 1;
1145 /* If not, post it in the Aide> room */
1146 if (successful_bounce == 0) {
1147 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1150 /* Free up the memory we used */
1151 if (valid != NULL) {
1156 CtdlFreeMessage(bmsg);
1157 lprintf(9, "Done processing bounces\n");
1162 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1163 * set of delivery instructions for completed deliveries and remove them.
1165 * It returns the number of incomplete deliveries remaining.
1167 int smtp_purge_completed_deliveries(char *instr) {
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 extract(addr, buf, 1);
1183 status = extract_int(buf, 2);
1184 extract(dsn, buf, 3);
1189 (!strcasecmp(key, "local"))
1190 || (!strcasecmp(key, "remote"))
1191 || (!strcasecmp(key, "ignet"))
1192 || (!strcasecmp(key, "room"))
1194 if (status == 2) completed = 1;
1199 remove_token(instr, i, '\n');
1212 * Called by smtp_do_queue() to handle an individual message.
1214 void smtp_do_procmsg(long msgnum, void *userdata) {
1215 struct CtdlMessage *msg;
1217 char *results = NULL;
1225 long text_msgid = (-1);
1226 int incomplete_deliveries_remaining;
1227 time_t attempted = 0L;
1228 time_t last_attempted = 0L;
1229 time_t retry = SMTP_RETRY_INTERVAL;
1231 lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1233 msg = CtdlFetchMessage(msgnum);
1235 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1239 instr = strdoop(msg->cm_fields['M']);
1240 CtdlFreeMessage(msg);
1242 /* Strip out the headers amd any other non-instruction line */
1243 lines = num_tokens(instr, '\n');
1244 for (i=0; i<lines; ++i) {
1245 extract_token(buf, instr, i, '\n');
1246 if (num_tokens(buf, '|') < 2) {
1247 remove_token(instr, i, '\n');
1253 /* Learn the message ID and find out about recent delivery attempts */
1254 lines = num_tokens(instr, '\n');
1255 for (i=0; i<lines; ++i) {
1256 extract_token(buf, instr, i, '\n');
1257 extract(key, buf, 0);
1258 if (!strcasecmp(key, "msgid")) {
1259 text_msgid = extract_long(buf, 1);
1261 if (!strcasecmp(key, "retry")) {
1262 /* double the retry interval after each attempt */
1263 retry = extract_long(buf, 1) * 2L;
1264 if (retry > SMTP_RETRY_MAX) {
1265 retry = SMTP_RETRY_MAX;
1267 remove_token(instr, i, '\n');
1269 if (!strcasecmp(key, "attempted")) {
1270 attempted = extract_long(buf, 1);
1271 if (attempted > last_attempted)
1272 last_attempted = attempted;
1277 * Postpone delivery if we've already tried recently.
1279 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1280 lprintf(7, "Retry time not yet reached.\n");
1287 * Bail out if there's no actual message associated with this
1289 if (text_msgid < 0L) {
1290 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1295 /* Plow through the instructions looking for 'remote' directives and
1296 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1297 * were experienced and it's time to try again)
1299 lines = num_tokens(instr, '\n');
1300 for (i=0; i<lines; ++i) {
1301 extract_token(buf, instr, i, '\n');
1302 extract(key, buf, 0);
1303 extract(addr, buf, 1);
1304 status = extract_int(buf, 2);
1305 extract(dsn, buf, 3);
1306 if ( (!strcasecmp(key, "remote"))
1307 && ((status==0)||(status==3)||(status==4)) ) {
1309 /* Remove this "remote" instruction from the set,
1310 * but replace the set's final newline if
1311 * remove_token() stripped it. It has to be there.
1313 remove_token(instr, i, '\n');
1314 if (instr[strlen(instr)-1] != '\n') {
1315 strcat(instr, "\n");
1320 lprintf(9, "SMTP: Trying <%s>\n", addr);
1321 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1323 if (results == NULL) {
1324 results = mallok(1024);
1325 memset(results, 0, 1024);
1328 results = reallok(results,
1329 strlen(results) + 1024);
1331 snprintf(&results[strlen(results)], 1024,
1333 key, addr, status, dsn);
1338 if (results != NULL) {
1339 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1340 strcat(instr, results);
1345 /* Generate 'bounce' messages */
1346 smtp_do_bounce(instr);
1348 /* Go through the delivery list, deleting completed deliveries */
1349 incomplete_deliveries_remaining =
1350 smtp_purge_completed_deliveries(instr);
1354 * No delivery instructions remain, so delete both the instructions
1355 * message and the message message.
1357 if (incomplete_deliveries_remaining <= 0) {
1358 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1359 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1364 * Uncompleted delivery instructions remain, so delete the old
1365 * instructions and replace with the updated ones.
1367 if (incomplete_deliveries_remaining > 0) {
1368 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1369 msg = mallok(sizeof(struct CtdlMessage));
1370 memset(msg, 0, sizeof(struct CtdlMessage));
1371 msg->cm_magic = CTDLMESSAGE_MAGIC;
1372 msg->cm_anon_type = MES_NORMAL;
1373 msg->cm_format_type = FMT_RFC822;
1374 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1375 snprintf(msg->cm_fields['M'],
1377 "Content-type: %s\n\n%s\n"
1380 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1382 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1383 CtdlFreeMessage(msg);
1393 * Run through the queue sending out messages.
1395 void smtp_do_queue(void) {
1396 static int doing_queue = 0;
1399 * This is a simple concurrency check to make sure only one queue run
1400 * is done at a time. We could do this with a mutex, but since we
1401 * don't really require extremely fine granularity here, we'll do it
1402 * with a static variable instead.
1404 if (doing_queue) return;
1408 * Go ahead and run the queue
1410 lprintf(7, "SMTP: processing outbound queue\n");
1412 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1413 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1416 CtdlForEachMessage(MSGS_ALL, 0L,
1417 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1419 lprintf(7, "SMTP: queue run completed\n");
1426 /*****************************************************************************/
1427 /* SMTP UTILITY COMMANDS */
1428 /*****************************************************************************/
1430 void cmd_smtp(char *argbuf) {
1437 if (CtdlAccessCheck(ac_aide)) return;
1439 extract(cmd, argbuf, 0);
1441 if (!strcasecmp(cmd, "mx")) {
1442 extract(node, argbuf, 1);
1443 num_mxhosts = getmx(buf, node);
1444 cprintf("%d %d MX hosts listed for %s\n",
1445 LISTING_FOLLOWS, num_mxhosts, node);
1446 for (i=0; i<num_mxhosts; ++i) {
1447 extract(node, buf, i);
1448 cprintf("%s\n", node);
1454 else if (!strcasecmp(cmd, "runqueue")) {
1456 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1461 cprintf("%d Invalid command.\n", ERROR+ILLEGAL_VALUE);
1468 * Initialize the SMTP outbound queue
1470 void smtp_init_spoolout(void) {
1471 struct ctdlroom qrbuf;
1474 * Create the room. This will silently fail if the room already
1475 * exists, and that's perfectly ok, because we want it to exist.
1477 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0);
1480 * Make sure it's set to be a "system room" so it doesn't show up
1481 * in the <K>nown rooms list for Aides.
1483 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1484 qrbuf.QRflags2 |= QR2_SYSTEM;
1492 /*****************************************************************************/
1493 /* MODULE INITIALIZATION STUFF */
1494 /*****************************************************************************/
1497 char *serv_smtp_init(void)
1499 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1504 CtdlRegisterServiceHook(0, /* ...and locally */
1509 smtp_init_spoolout();
1510 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1511 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");