4 * This module is an SMTP and ESMTP implementation for the Citadel system.
5 * It is compliant with all of the following:
7 * RFC 821 - Simple Mail Transfer Protocol
8 * RFC 876 - Survey of SMTP Implementations
9 * RFC 1047 - Duplicate messages and SMTP
10 * RFC 1854 - command pipelining
11 * RFC 1869 - Extended Simple Mail Transfer Protocol
12 * RFC 1870 - SMTP Service Extension for Message Size Declaration
13 * RFC 1893 - Enhanced Mail System Status Codes
14 * RFC 2033 - Local Mail Transfer Protocol
15 * RFC 2034 - SMTP Service Extension for Returning Enhanced Error Codes
16 * RFC 2197 - SMTP Service Extension for Command Pipelining
17 * RFC 2476 - Message Submission
18 * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
19 * RFC 2554 - SMTP Service Extension for Authentication
20 * RFC 2821 - Simple Mail Transfer Protocol
21 * RFC 2822 - Internet Message Format
22 * RFC 2920 - SMTP Service Extension for Command Pipelining
24 * The VRFY and EXPN commands have been removed from this implementation
25 * because nobody uses these commands anymore, except for spammers.
37 #include <sys/types.h>
40 #if TIME_WITH_SYS_TIME
41 # include <sys/time.h>
45 # include <sys/time.h>
55 #include <sys/socket.h>
56 #include <netinet/in.h>
57 #include <arpa/inet.h>
60 #include "citserver.h"
70 #include "internet_addressing.h"
73 #include "clientsocket.h"
74 #include "locate_host.h"
75 #include "citadel_dirs.h"
84 #include "ctdl_module.h"
88 struct citsmtp { /* Information about the current session */
93 int number_of_recipients;
95 int message_originated_locally;
101 enum { /* Command states for login authentication */
108 #define SMTP CC->SMTP
111 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
115 /*****************************************************************************/
116 /* SMTP SERVER (INBOUND) STUFF */
117 /*****************************************************************************/
121 * Here's where our SMTP session begins its happy day.
123 void smtp_greeting(int is_msa)
125 char message_to_spammer[1024];
127 strcpy(CC->cs_clientname, "SMTP session");
128 CC->internal_pgm = 1;
129 CC->cs_flags |= CS_STEALTH;
130 SMTP = malloc(sizeof(struct citsmtp));
131 memset(SMTP, 0, sizeof(struct citsmtp));
132 SMTP->is_msa = is_msa;
134 /* If this config option is set, reject connections from problem
135 * addresses immediately instead of after they execute a RCPT
137 if ( (config.c_rbl_at_greeting) && (SMTP->is_msa == 0) ) {
138 if (rbl_check(message_to_spammer)) {
139 cprintf("550 %s\r\n", message_to_spammer);
141 /* no need to free_recipients(valid), it's not allocated yet */
146 /* Otherwise we're either clean or we check later. */
148 if (CC->nologin==1) {
149 cprintf("500 Too many users are already online (maximum is %d)\r\n",
153 /* no need to free_recipients(valid), it's not allocated yet */
157 /* Note: the FQDN *must* appear as the first thing after the 220 code.
158 * Some clients (including citmail.c) depend on it being there.
160 cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn);
165 * SMTPS is just like SMTP, except it goes crypto right away.
167 void smtps_greeting(void) {
168 CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
174 * SMTP MSA port requires authentication.
176 void smtp_msa_greeting(void) {
182 * LMTP is like SMTP but with some extra bonus footage added.
184 void lmtp_greeting(void) {
191 * Generic SMTP MTA greeting
193 void smtp_mta_greeting(void) {
199 * We also have an unfiltered LMTP socket that bypasses spam filters.
201 void lmtp_unfiltered_greeting(void) {
204 SMTP->is_unfiltered = 1;
209 * Login greeting common to all auth methods
211 void smtp_auth_greeting(void) {
212 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
213 lprintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
214 CC->internal_pgm = 0;
215 CC->cs_flags &= ~CS_STEALTH;
220 * Implement HELO and EHLO commands.
222 * which_command: 0=HELO, 1=EHLO, 2=LHLO
224 void smtp_hello(char *argbuf, int which_command) {
226 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
228 if ( (which_command != 2) && (SMTP->is_lmtp) ) {
229 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
233 if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
234 cprintf("500 LHLO is only allowed when running LMTP\r\n");
238 if (which_command == 0) {
239 cprintf("250 Hello %s (%s [%s])\r\n",
246 if (which_command == 1) {
247 cprintf("250-Hello %s (%s [%s])\r\n",
254 cprintf("250-Greetings and joyous salutations.\r\n");
256 cprintf("250-HELP\r\n");
257 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
261 /* Only offer the PIPELINING command if TLS is inactive,
262 * because of flow control issues. Also, avoid offering TLS
263 * if TLS is already active. Finally, we only offer TLS on
264 * the SMTP-MSA port, not on the SMTP-MTA port, due to
265 * questionable reliability of TLS in certain sending MTA's.
267 if ( (!CC->redirect_ssl) && (SMTP->is_msa) ) {
268 cprintf("250-PIPELINING\r\n");
269 cprintf("250-STARTTLS\r\n");
272 #else /* HAVE_OPENSSL */
274 /* Non SSL enabled server, so always offer PIPELINING. */
275 cprintf("250-PIPELINING\r\n");
277 #endif /* HAVE_OPENSSL */
279 cprintf("250-AUTH LOGIN PLAIN\r\n");
280 cprintf("250-AUTH=LOGIN PLAIN\r\n");
282 cprintf("250 ENHANCEDSTATUSCODES\r\n");
289 * Implement HELP command.
291 void smtp_help(void) {
292 cprintf("214-Commands accepted:\r\n");
293 cprintf("214- DATA\r\n");
294 cprintf("214- EHLO\r\n");
295 cprintf("214- HELO\r\n");
296 cprintf("214- HELP\r\n");
297 cprintf("214- MAIL\r\n");
298 cprintf("214- NOOP\r\n");
299 cprintf("214- QUIT\r\n");
300 cprintf("214- RCPT\r\n");
301 cprintf("214- RSET\r\n");
309 void smtp_get_user(char *argbuf) {
313 CtdlDecodeBase64(username, argbuf, SIZ);
314 /* lprintf(CTDL_DEBUG, "Trying <%s>\n", username); */
315 if (CtdlLoginExistingUser(NULL, username) == login_ok) {
316 CtdlEncodeBase64(buf, "Password:", 9);
317 cprintf("334 %s\r\n", buf);
318 SMTP->command_state = smtp_password;
321 cprintf("500 5.7.0 No such user.\r\n");
322 SMTP->command_state = smtp_command;
330 void smtp_get_pass(char *argbuf) {
333 CtdlDecodeBase64(password, argbuf, SIZ);
334 /* lprintf(CTDL_DEBUG, "Trying <%s>\n", password); */
335 if (CtdlTryPassword(password) == pass_ok) {
336 smtp_auth_greeting();
339 cprintf("535 5.7.0 Authentication failed.\r\n");
341 SMTP->command_state = smtp_command;
346 * Back end for PLAIN auth method (either inline or multistate)
348 void smtp_try_plain(char *encoded_authstring) {
349 char decoded_authstring[1024];
355 CtdlDecodeBase64(decoded_authstring, encoded_authstring, strlen(encoded_authstring) );
356 safestrncpy(ident, decoded_authstring, sizeof ident);
357 safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
358 safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
360 SMTP->command_state = smtp_command;
362 if (strlen(ident) > 0) {
363 result = CtdlLoginExistingUser(user, ident);
366 result = CtdlLoginExistingUser(NULL, user);
369 if (result == login_ok) {
370 if (CtdlTryPassword(pass) == pass_ok) {
371 smtp_auth_greeting();
375 cprintf("504 5.7.4 Authentication failed.\r\n");
380 * Attempt to perform authenticated SMTP
382 void smtp_auth(char *argbuf) {
383 char username_prompt[64];
385 char encoded_authstring[1024];
388 cprintf("504 5.7.4 Already logged in.\r\n");
392 extract_token(method, argbuf, 0, ' ', sizeof method);
394 if (!strncasecmp(method, "login", 5) ) {
395 if (strlen(argbuf) >= 7) {
396 smtp_get_user(&argbuf[6]);
399 CtdlEncodeBase64(username_prompt, "Username:", 9);
400 cprintf("334 %s\r\n", username_prompt);
401 SMTP->command_state = smtp_user;
406 if (!strncasecmp(method, "plain", 5) ) {
407 if (num_tokens(argbuf, ' ') < 2) {
409 SMTP->command_state = smtp_plain;
413 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
415 smtp_try_plain(encoded_authstring);
419 if (strncasecmp(method, "login", 5) ) {
420 cprintf("504 5.7.4 Unknown authentication method.\r\n");
428 * Implements the RSET (reset state) command.
429 * Currently this just zeroes out the state buffer. If pointers to data
430 * allocated with malloc() are ever placed in the state buffer, we have to
431 * be sure to free() them first!
433 * Set do_response to nonzero to output the SMTP RSET response code.
435 void smtp_rset(int do_response) {
440 * Our entire SMTP state is discarded when a RSET command is issued,
441 * but we need to preserve this one little piece of information, so
442 * we save it for later.
444 is_lmtp = SMTP->is_lmtp;
445 is_unfiltered = SMTP->is_unfiltered;
447 memset(SMTP, 0, sizeof(struct citsmtp));
450 * It is somewhat ambiguous whether we want to log out when a RSET
451 * command is issued. Here's the code to do it. It is commented out
452 * because some clients (such as Pine) issue RSET commands before
453 * each message, but still expect to be logged in.
455 * if (CC->logged_in) {
461 * Reinstate this little piece of information we saved (see above).
463 SMTP->is_lmtp = is_lmtp;
464 SMTP->is_unfiltered = is_unfiltered;
467 cprintf("250 2.0.0 Zap!\r\n");
472 * Clear out the portions of the state buffer that need to be cleared out
473 * after the DATA command finishes.
475 void smtp_data_clear(void) {
476 strcpy(SMTP->from, "");
477 strcpy(SMTP->recipients, "");
478 SMTP->number_of_recipients = 0;
479 SMTP->delivery_mode = 0;
480 SMTP->message_originated_locally = 0;
486 * Implements the "MAIL From:" command
488 void smtp_mail(char *argbuf) {
493 if (strlen(SMTP->from) != 0) {
494 cprintf("503 5.1.0 Only one sender permitted\r\n");
498 if (strncasecmp(argbuf, "From:", 5)) {
499 cprintf("501 5.1.7 Syntax error\r\n");
503 strcpy(SMTP->from, &argbuf[5]);
505 if (haschar(SMTP->from, '<') > 0) {
506 stripallbut(SMTP->from, '<', '>');
509 /* We used to reject empty sender names, until it was brought to our
510 * attention that RFC1123 5.2.9 requires that this be allowed. So now
511 * we allow it, but replace the empty string with a fake
512 * address so we don't have to contend with the empty string causing
513 * other code to fail when it's expecting something there.
515 if (strlen(SMTP->from) == 0) {
516 strcpy(SMTP->from, "someone@somewhere.org");
519 /* If this SMTP connection is from a logged-in user, force the 'from'
520 * to be the user's Internet e-mail address as Citadel knows it.
523 safestrncpy(SMTP->from, CC->cs_inet_email, sizeof SMTP->from);
524 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
525 SMTP->message_originated_locally = 1;
529 else if (SMTP->is_lmtp) {
530 /* Bypass forgery checking for LMTP */
533 /* Otherwise, make sure outsiders aren't trying to forge mail from
534 * this system (unless, of course, c_allow_spoofing is enabled)
536 else if (config.c_allow_spoofing == 0) {
537 process_rfc822_addr(SMTP->from, user, node, name);
538 if (CtdlHostAlias(node) != hostalias_nomatch) {
540 "You must log in to send mail from %s\r\n",
542 strcpy(SMTP->from, "");
547 cprintf("250 2.0.0 Sender ok\r\n");
553 * Implements the "RCPT To:" command
555 void smtp_rcpt(char *argbuf) {
557 char message_to_spammer[SIZ];
558 struct recptypes *valid = NULL;
560 if (strlen(SMTP->from) == 0) {
561 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
565 if (strncasecmp(argbuf, "To:", 3)) {
566 cprintf("501 5.1.7 Syntax error\r\n");
570 if ( (SMTP->is_msa) && (!CC->logged_in) ) {
572 "You must log in to send mail on this port.\r\n");
573 strcpy(SMTP->from, "");
577 safestrncpy(recp, &argbuf[3], sizeof recp);
579 stripallbut(recp, '<', '>');
581 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
582 cprintf("452 4.5.3 Too many recipients\r\n");
587 if ( (!CC->logged_in) /* Don't RBL authenticated users */
588 && (!SMTP->is_lmtp) ) { /* Don't RBL LMTP clients */
589 if (config.c_rbl_at_greeting == 0) { /* Don't RBL again if we already did it */
590 if (rbl_check(message_to_spammer)) {
591 cprintf("550 %s\r\n", message_to_spammer);
592 /* no need to free_recipients(valid), it's not allocated yet */
598 valid = validate_recipients(recp);
599 if (valid->num_error != 0) {
600 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
601 free_recipients(valid);
605 if (valid->num_internet > 0) {
607 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
608 cprintf("551 5.7.1 <%s> - you do not have permission to send Internet mail\r\n", recp);
609 free_recipients(valid);
615 if (valid->num_internet > 0) {
616 if ( (SMTP->message_originated_locally == 0)
617 && (SMTP->is_lmtp == 0) ) {
618 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
619 free_recipients(valid);
624 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
625 if (strlen(SMTP->recipients) > 0) {
626 strcat(SMTP->recipients, ",");
628 strcat(SMTP->recipients, recp);
629 SMTP->number_of_recipients += 1;
631 free_recipients(valid);
639 * Implements the DATA command
641 void smtp_data(void) {
643 struct CtdlMessage *msg = NULL;
646 struct recptypes *valid;
651 if (strlen(SMTP->from) == 0) {
652 cprintf("503 5.5.1 Need MAIL command first.\r\n");
656 if (SMTP->number_of_recipients < 1) {
657 cprintf("503 5.5.1 Need RCPT command first.\r\n");
661 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
663 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
666 if (body != NULL) snprintf(body, 4096,
667 "Received: from %s (%s [%s])\n"
675 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
678 "Unable to save message: internal error.\r\n");
682 lprintf(CTDL_DEBUG, "Converting message...\n");
683 msg = convert_internet_message(body);
685 /* If the user is locally authenticated, FORCE the From: header to
686 * show up as the real sender. Yes, this violates the RFC standard,
687 * but IT MAKES SENSE. If you prefer strict RFC adherence over
688 * common sense, you can disable this in the configuration.
690 * We also set the "message room name" ('O' field) to MAILROOM
691 * (which is Mail> on most systems) to prevent it from getting set
692 * to something ugly like "0000058008.Sent Items>" when the message
693 * is read with a Citadel client.
695 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
696 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
697 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
698 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
699 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
700 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
701 msg->cm_fields['A'] = strdup(CC->user.fullname);
702 msg->cm_fields['N'] = strdup(config.c_nodename);
703 msg->cm_fields['H'] = strdup(config.c_humannode);
704 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
705 msg->cm_fields['O'] = strdup(MAILROOM);
708 /* Set the "envelope from" address */
709 if (msg->cm_fields['P'] != NULL) {
710 free(msg->cm_fields['P']);
712 msg->cm_fields['P'] = strdup(SMTP->from);
714 /* Set the "envelope to" address */
715 if (msg->cm_fields['V'] != NULL) {
716 free(msg->cm_fields['V']);
718 msg->cm_fields['V'] = strdup(SMTP->recipients);
720 /* Submit the message into the Citadel system. */
721 valid = validate_recipients(SMTP->recipients);
723 /* If there are modules that want to scan this message before final
724 * submission (such as virus checkers or spam filters), call them now
725 * and give them an opportunity to reject the message.
727 if (SMTP->is_unfiltered) {
731 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
734 if (scan_errors > 0) { /* We don't want this message! */
736 if (msg->cm_fields['0'] == NULL) {
737 msg->cm_fields['0'] = strdup(
738 "5.7.1 Message rejected by filter");
741 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
744 else { /* Ok, we'll accept this message. */
745 msgnum = CtdlSubmitMsg(msg, valid, "");
747 sprintf(result, "250 2.0.0 Message accepted.\r\n");
750 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
754 /* For SMTP and ESTMP, just print the result message. For LMTP, we
755 * have to print one result message for each recipient. Since there
756 * is nothing in Citadel which would cause different recipients to
757 * have different results, we can get away with just spitting out the
758 * same message once for each recipient.
761 for (i=0; i<SMTP->number_of_recipients; ++i) {
762 cprintf("%s", result);
766 cprintf("%s", result);
769 /* Write something to the syslog (which may or may not be where the
770 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
773 syslog((LOG_MAIL | LOG_INFO),
774 "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
777 SMTP->number_of_recipients,
785 CtdlFreeMessage(msg);
786 free_recipients(valid);
787 smtp_data_clear(); /* clear out the buffers now */
792 * implements the STARTTLS command (Citadel API version)
794 void smtp_starttls(void)
796 char ok_response[SIZ];
797 char nosup_response[SIZ];
798 char error_response[SIZ];
801 "200 2.0.0 Begin TLS negotiation now\r\n");
802 sprintf(nosup_response,
803 "554 5.7.3 TLS not supported here\r\n");
804 sprintf(error_response,
805 "554 5.7.3 Internal error\r\n");
806 CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
813 * Main command loop for SMTP sessions.
815 void smtp_command_loop(void) {
819 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
820 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
821 lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
825 lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
826 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
828 if (SMTP->command_state == smtp_user) {
829 smtp_get_user(cmdbuf);
832 else if (SMTP->command_state == smtp_password) {
833 smtp_get_pass(cmdbuf);
836 else if (SMTP->command_state == smtp_plain) {
837 smtp_try_plain(cmdbuf);
840 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
841 smtp_auth(&cmdbuf[5]);
844 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
848 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
849 smtp_hello(&cmdbuf[5], 0);
852 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
853 smtp_hello(&cmdbuf[5], 1);
856 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
857 smtp_hello(&cmdbuf[5], 2);
860 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
864 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
865 smtp_mail(&cmdbuf[5]);
868 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
869 cprintf("250 NOOP\r\n");
872 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
873 cprintf("221 Goodbye...\r\n");
878 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
879 smtp_rcpt(&cmdbuf[5]);
882 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
886 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
891 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
900 /*****************************************************************************/
901 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
902 /*****************************************************************************/
909 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
912 void smtp_try(const char *key, const char *addr, int *status,
913 char *dsn, size_t n, long msgnum)
920 char user[1024], node[1024], name[1024];
933 /* Parse out the host portion of the recipient address */
934 process_rfc822_addr(addr, user, node, name);
936 lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
939 /* Load the message out of the database */
940 CC->redirect_buffer = malloc(SIZ);
941 CC->redirect_len = 0;
942 CC->redirect_alloc = SIZ;
943 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
944 msgtext = CC->redirect_buffer;
945 msg_size = CC->redirect_len;
946 CC->redirect_buffer = NULL;
947 CC->redirect_len = 0;
948 CC->redirect_alloc = 0;
950 /* Extract something to send later in the 'MAIL From:' command */
951 strcpy(mailfrom, "");
955 if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
958 if (!strncasecmp(buf, "From:", 5)) {
959 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
961 for (i=0; i<strlen(mailfrom); ++i) {
962 if (!isprint(mailfrom[i])) {
963 strcpy(&mailfrom[i], &mailfrom[i+1]);
968 /* Strip out parenthesized names */
971 for (i=0; i<strlen(mailfrom); ++i) {
972 if (mailfrom[i] == '(') lp = i;
973 if (mailfrom[i] == ')') rp = i;
975 if ((lp>0)&&(rp>lp)) {
976 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
979 /* Prefer brokketized names */
982 for (i=0; i<strlen(mailfrom); ++i) {
983 if (mailfrom[i] == '<') lp = i;
984 if (mailfrom[i] == '>') rp = i;
986 if ( (lp>=0) && (rp>lp) ) {
988 strcpy(mailfrom, &mailfrom[lp]);
993 } while (scan_done == 0);
994 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
995 stripallbut(mailfrom, '<', '>');
997 /* Figure out what mail exchanger host we have to connect to */
998 num_mxhosts = getmx(mxhosts, node);
999 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
1000 if (num_mxhosts < 1) {
1002 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
1007 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
1009 extract_token(buf, mxhosts, mx, '|', sizeof buf);
1010 strcpy(mx_user, "");
1011 strcpy(mx_pass, "");
1012 if (num_tokens(buf, '@') > 1) {
1013 strcpy (mx_user, buf);
1014 endpart = strrchr(mx_user, '@');
1016 strcpy (mx_host, endpart + 1);
1017 endpart = strrchr(mx_user, ':');
1018 if (endpart != NULL) {
1019 strcpy(mx_pass, endpart+1);
1024 strcpy (mx_host, buf);
1025 endpart = strrchr(mx_host, ':');
1028 strcpy(mx_port, endpart + 1);
1031 strcpy(mx_port, "25");
1033 lprintf(CTDL_DEBUG, "Trying %s : %s ...\n", mx_host, mx_port);
1034 sock = sock_connect(mx_host, mx_port, "tcp");
1035 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
1036 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
1039 snprintf(dsn, SIZ, "%s", strerror(errno));
1042 snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
1048 *status = 4; /* dsn is already filled in */
1052 /* Process the SMTP greeting from the server */
1053 if (ml_sock_gets(sock, buf) < 0) {
1055 strcpy(dsn, "Connection broken during SMTP conversation");
1058 lprintf(CTDL_DEBUG, "<%s\n", buf);
1059 if (buf[0] != '2') {
1060 if (buf[0] == '4') {
1062 safestrncpy(dsn, &buf[4], 1023);
1067 safestrncpy(dsn, &buf[4], 1023);
1072 /* At this point we know we are talking to a real SMTP server */
1074 /* Do a EHLO command. If it fails, try the HELO command. */
1075 snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
1076 lprintf(CTDL_DEBUG, ">%s", buf);
1077 sock_write(sock, buf, strlen(buf));
1078 if (ml_sock_gets(sock, buf) < 0) {
1080 strcpy(dsn, "Connection broken during SMTP HELO");
1083 lprintf(CTDL_DEBUG, "<%s\n", buf);
1084 if (buf[0] != '2') {
1085 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1086 lprintf(CTDL_DEBUG, ">%s", buf);
1087 sock_write(sock, buf, strlen(buf));
1088 if (ml_sock_gets(sock, buf) < 0) {
1090 strcpy(dsn, "Connection broken during SMTP HELO");
1094 if (buf[0] != '2') {
1095 if (buf[0] == '4') {
1097 safestrncpy(dsn, &buf[4], 1023);
1102 safestrncpy(dsn, &buf[4], 1023);
1107 /* Do an AUTH command if necessary */
1108 if (strlen(mx_user) > 0) {
1110 sprintf(buf, "%s%c%s%c%s", mx_user, '\0', mx_user, '\0', mx_pass);
1111 CtdlEncodeBase64(encoded, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 2);
1112 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", encoded);
1113 lprintf(CTDL_DEBUG, ">%s", buf);
1114 sock_write(sock, buf, strlen(buf));
1115 if (ml_sock_gets(sock, buf) < 0) {
1117 strcpy(dsn, "Connection broken during SMTP AUTH");
1120 lprintf(CTDL_DEBUG, "<%s\n", buf);
1121 if (buf[0] != '2') {
1122 if (buf[0] == '4') {
1124 safestrncpy(dsn, &buf[4], 1023);
1129 safestrncpy(dsn, &buf[4], 1023);
1135 /* previous command succeeded, now try the MAIL From: command */
1136 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1137 lprintf(CTDL_DEBUG, ">%s", buf);
1138 sock_write(sock, buf, strlen(buf));
1139 if (ml_sock_gets(sock, buf) < 0) {
1141 strcpy(dsn, "Connection broken during SMTP MAIL");
1144 lprintf(CTDL_DEBUG, "<%s\n", buf);
1145 if (buf[0] != '2') {
1146 if (buf[0] == '4') {
1148 safestrncpy(dsn, &buf[4], 1023);
1153 safestrncpy(dsn, &buf[4], 1023);
1158 /* MAIL succeeded, now try the RCPT To: command */
1159 snprintf(buf, sizeof buf, "RCPT To: <%s@%s>\r\n", user, node);
1160 lprintf(CTDL_DEBUG, ">%s", buf);
1161 sock_write(sock, buf, strlen(buf));
1162 if (ml_sock_gets(sock, buf) < 0) {
1164 strcpy(dsn, "Connection broken during SMTP RCPT");
1167 lprintf(CTDL_DEBUG, "<%s\n", buf);
1168 if (buf[0] != '2') {
1169 if (buf[0] == '4') {
1171 safestrncpy(dsn, &buf[4], 1023);
1176 safestrncpy(dsn, &buf[4], 1023);
1181 /* RCPT succeeded, now try the DATA command */
1182 lprintf(CTDL_DEBUG, ">DATA\n");
1183 sock_write(sock, "DATA\r\n", 6);
1184 if (ml_sock_gets(sock, buf) < 0) {
1186 strcpy(dsn, "Connection broken during SMTP DATA");
1189 lprintf(CTDL_DEBUG, "<%s\n", buf);
1190 if (buf[0] != '3') {
1191 if (buf[0] == '4') {
1193 safestrncpy(dsn, &buf[4], 1023);
1198 safestrncpy(dsn, &buf[4], 1023);
1203 /* If we reach this point, the server is expecting data */
1204 sock_write(sock, msgtext, msg_size);
1205 if (msgtext[msg_size-1] != 10) {
1206 lprintf(CTDL_WARNING, "Possible problem: message did not "
1207 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1211 sock_write(sock, ".\r\n", 3);
1212 if (ml_sock_gets(sock, buf) < 0) {
1214 strcpy(dsn, "Connection broken during SMTP message transmit");
1217 lprintf(CTDL_DEBUG, "%s\n", buf);
1218 if (buf[0] != '2') {
1219 if (buf[0] == '4') {
1221 safestrncpy(dsn, &buf[4], 1023);
1226 safestrncpy(dsn, &buf[4], 1023);
1232 safestrncpy(dsn, &buf[4], 1023);
1235 lprintf(CTDL_DEBUG, ">QUIT\n");
1236 sock_write(sock, "QUIT\r\n", 6);
1237 ml_sock_gets(sock, buf);
1238 lprintf(CTDL_DEBUG, "<%s\n", buf);
1239 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1242 bail: free(msgtext);
1245 /* Write something to the syslog (which may or may not be where the
1246 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1248 if (enable_syslog) {
1249 syslog((LOG_MAIL | LOG_INFO),
1250 "%ld: to=<%s>, relay=%s, stat=%s",
1264 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1265 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1266 * a "bounce" message (delivery status notification).
1268 void smtp_do_bounce(char *instr) {
1276 char bounceto[1024];
1278 int num_bounces = 0;
1279 int bounce_this = 0;
1280 long bounce_msgid = (-1);
1281 time_t submitted = 0L;
1282 struct CtdlMessage *bmsg = NULL;
1284 struct recptypes *valid;
1285 int successful_bounce = 0;
1291 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1292 strcpy(bounceto, "");
1293 sprintf(boundary, "=_Citadel_Multipart_%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
1294 lines = num_tokens(instr, '\n');
1296 /* See if it's time to give up on delivery of this message */
1297 for (i=0; i<lines; ++i) {
1298 extract_token(buf, instr, i, '\n', sizeof buf);
1299 extract_token(key, buf, 0, '|', sizeof key);
1300 extract_token(addr, buf, 1, '|', sizeof addr);
1301 if (!strcasecmp(key, "submitted")) {
1302 submitted = atol(addr);
1306 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1310 /* Start building our bounce message */
1312 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1313 if (bmsg == NULL) return;
1314 memset(bmsg, 0, sizeof(struct CtdlMessage));
1316 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1317 bmsg->cm_anon_type = MES_NORMAL;
1318 bmsg->cm_format_type = FMT_RFC822;
1319 bmsg->cm_fields['A'] = strdup("Citadel");
1320 bmsg->cm_fields['O'] = strdup(MAILROOM);
1321 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1322 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1323 bmsg->cm_fields['M'] = malloc(1024);
1325 strcpy(bmsg->cm_fields['M'], "Content-type: multipart/mixed; boundary=\"");
1326 strcat(bmsg->cm_fields['M'], boundary);
1327 strcat(bmsg->cm_fields['M'], "\"\r\n");
1328 strcat(bmsg->cm_fields['M'], "MIME-Version: 1.0\r\n");
1329 strcat(bmsg->cm_fields['M'], "X-Mailer: " CITADEL "\r\n");
1330 strcat(bmsg->cm_fields['M'], "\r\nThis is a multipart message in MIME format.\r\n\r\n");
1331 strcat(bmsg->cm_fields['M'], "--");
1332 strcat(bmsg->cm_fields['M'], boundary);
1333 strcat(bmsg->cm_fields['M'], "\r\n");
1334 strcat(bmsg->cm_fields['M'], "Content-type: text/plain\r\n\r\n");
1336 if (give_up) strcat(bmsg->cm_fields['M'],
1337 "A message you sent could not be delivered to some or all of its recipients\n"
1338 "due to prolonged unavailability of its destination(s).\n"
1339 "Giving up on the following addresses:\n\n"
1342 else strcat(bmsg->cm_fields['M'],
1343 "A message you sent could not be delivered to some or all of its recipients.\n"
1344 "The following addresses were undeliverable:\n\n"
1348 * Now go through the instructions checking for stuff.
1350 for (i=0; i<lines; ++i) {
1351 extract_token(buf, instr, i, '\n', sizeof buf);
1352 extract_token(key, buf, 0, '|', sizeof key);
1353 extract_token(addr, buf, 1, '|', sizeof addr);
1354 status = extract_int(buf, 2);
1355 extract_token(dsn, buf, 3, '|', sizeof dsn);
1358 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1359 key, addr, status, dsn);
1361 if (!strcasecmp(key, "bounceto")) {
1362 strcpy(bounceto, addr);
1365 if (!strcasecmp(key, "msgid")) {
1366 omsgid = atol(addr);
1369 if (!strcasecmp(key, "remote")) {
1370 if (status == 5) bounce_this = 1;
1371 if (give_up) bounce_this = 1;
1377 if (bmsg->cm_fields['M'] == NULL) {
1378 lprintf(CTDL_ERR, "ERROR ... M field is null "
1379 "(%s:%d)\n", __FILE__, __LINE__);
1382 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1383 strlen(bmsg->cm_fields['M']) + 1024 );
1384 strcat(bmsg->cm_fields['M'], addr);
1385 strcat(bmsg->cm_fields['M'], ": ");
1386 strcat(bmsg->cm_fields['M'], dsn);
1387 strcat(bmsg->cm_fields['M'], "\r\n");
1389 remove_token(instr, i, '\n');
1395 /* Attach the original message */
1397 strcat(bmsg->cm_fields['M'], "--");
1398 strcat(bmsg->cm_fields['M'], boundary);
1399 strcat(bmsg->cm_fields['M'], "\r\n");
1400 strcat(bmsg->cm_fields['M'], "Content-type: message/rfc822\r\n");
1401 strcat(bmsg->cm_fields['M'], "Content-Transfer-Encoding: 7bit\r\n");
1402 strcat(bmsg->cm_fields['M'], "Content-Disposition: inline\r\n");
1403 strcat(bmsg->cm_fields['M'], "\r\n");
1405 CC->redirect_buffer = malloc(SIZ);
1406 CC->redirect_len = 0;
1407 CC->redirect_alloc = SIZ;
1408 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
1409 omsgtext = CC->redirect_buffer;
1410 omsgsize = CC->redirect_len;
1411 CC->redirect_buffer = NULL;
1412 CC->redirect_len = 0;
1413 CC->redirect_alloc = 0;
1414 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1415 (strlen(bmsg->cm_fields['M']) + omsgsize + 1024) );
1416 strcat(bmsg->cm_fields['M'], omsgtext);
1420 /* Close the multipart MIME scope */
1421 strcat(bmsg->cm_fields['M'], "--");
1422 strcat(bmsg->cm_fields['M'], boundary);
1423 strcat(bmsg->cm_fields['M'], "--\r\n");
1425 /* Deliver the bounce if there's anything worth mentioning */
1426 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1427 if (num_bounces > 0) {
1429 /* First try the user who sent the message */
1430 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1431 if (strlen(bounceto) == 0) {
1432 lprintf(CTDL_ERR, "No bounce address specified\n");
1433 bounce_msgid = (-1L);
1436 /* Can we deliver the bounce to the original sender? */
1437 valid = validate_recipients(bounceto);
1438 if (valid != NULL) {
1439 if (valid->num_error == 0) {
1440 CtdlSubmitMsg(bmsg, valid, "");
1441 successful_bounce = 1;
1445 /* If not, post it in the Aide> room */
1446 if (successful_bounce == 0) {
1447 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1450 /* Free up the memory we used */
1451 if (valid != NULL) {
1452 free_recipients(valid);
1456 CtdlFreeMessage(bmsg);
1457 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1462 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1463 * set of delivery instructions for completed deliveries and remove them.
1465 * It returns the number of incomplete deliveries remaining.
1467 int smtp_purge_completed_deliveries(char *instr) {
1478 lines = num_tokens(instr, '\n');
1479 for (i=0; i<lines; ++i) {
1480 extract_token(buf, instr, i, '\n', sizeof buf);
1481 extract_token(key, buf, 0, '|', sizeof key);
1482 extract_token(addr, buf, 1, '|', sizeof addr);
1483 status = extract_int(buf, 2);
1484 extract_token(dsn, buf, 3, '|', sizeof dsn);
1488 if (!strcasecmp(key, "remote")) {
1489 if (status == 2) completed = 1;
1494 remove_token(instr, i, '\n');
1507 * Called by smtp_do_queue() to handle an individual message.
1509 void smtp_do_procmsg(long msgnum, void *userdata) {
1510 struct CtdlMessage *msg = NULL;
1512 char *results = NULL;
1520 long text_msgid = (-1);
1521 int incomplete_deliveries_remaining;
1522 time_t attempted = 0L;
1523 time_t last_attempted = 0L;
1524 time_t retry = SMTP_RETRY_INTERVAL;
1526 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1528 msg = CtdlFetchMessage(msgnum, 1);
1530 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1534 instr = strdup(msg->cm_fields['M']);
1535 CtdlFreeMessage(msg);
1537 /* Strip out the headers amd any other non-instruction line */
1538 lines = num_tokens(instr, '\n');
1539 for (i=0; i<lines; ++i) {
1540 extract_token(buf, instr, i, '\n', sizeof buf);
1541 if (num_tokens(buf, '|') < 2) {
1542 remove_token(instr, i, '\n');
1548 /* Learn the message ID and find out about recent delivery attempts */
1549 lines = num_tokens(instr, '\n');
1550 for (i=0; i<lines; ++i) {
1551 extract_token(buf, instr, i, '\n', sizeof buf);
1552 extract_token(key, buf, 0, '|', sizeof key);
1553 if (!strcasecmp(key, "msgid")) {
1554 text_msgid = extract_long(buf, 1);
1556 if (!strcasecmp(key, "retry")) {
1557 /* double the retry interval after each attempt */
1558 retry = extract_long(buf, 1) * 2L;
1559 if (retry > SMTP_RETRY_MAX) {
1560 retry = SMTP_RETRY_MAX;
1562 remove_token(instr, i, '\n');
1564 if (!strcasecmp(key, "attempted")) {
1565 attempted = extract_long(buf, 1);
1566 if (attempted > last_attempted)
1567 last_attempted = attempted;
1572 * Postpone delivery if we've already tried recently.
1574 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1575 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1582 * Bail out if there's no actual message associated with this
1584 if (text_msgid < 0L) {
1585 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1590 /* Plow through the instructions looking for 'remote' directives and
1591 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1592 * were experienced and it's time to try again)
1594 lines = num_tokens(instr, '\n');
1595 for (i=0; i<lines; ++i) {
1596 extract_token(buf, instr, i, '\n', sizeof buf);
1597 extract_token(key, buf, 0, '|', sizeof key);
1598 extract_token(addr, buf, 1, '|', sizeof addr);
1599 status = extract_int(buf, 2);
1600 extract_token(dsn, buf, 3, '|', sizeof dsn);
1601 if ( (!strcasecmp(key, "remote"))
1602 && ((status==0)||(status==3)||(status==4)) ) {
1604 /* Remove this "remote" instruction from the set,
1605 * but replace the set's final newline if
1606 * remove_token() stripped it. It has to be there.
1608 remove_token(instr, i, '\n');
1609 if (instr[strlen(instr)-1] != '\n') {
1610 strcat(instr, "\n");
1615 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1616 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1618 if (results == NULL) {
1619 results = malloc(1024);
1620 memset(results, 0, 1024);
1623 results = realloc(results,
1624 strlen(results) + 1024);
1626 snprintf(&results[strlen(results)], 1024,
1628 key, addr, status, dsn);
1633 if (results != NULL) {
1634 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1635 strcat(instr, results);
1640 /* Generate 'bounce' messages */
1641 smtp_do_bounce(instr);
1643 /* Go through the delivery list, deleting completed deliveries */
1644 incomplete_deliveries_remaining =
1645 smtp_purge_completed_deliveries(instr);
1649 * No delivery instructions remain, so delete both the instructions
1650 * message and the message message.
1652 if (incomplete_deliveries_remaining <= 0) {
1654 delmsgs[0] = msgnum;
1655 delmsgs[1] = text_msgid;
1656 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
1660 * Uncompleted delivery instructions remain, so delete the old
1661 * instructions and replace with the updated ones.
1663 if (incomplete_deliveries_remaining > 0) {
1664 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
1665 msg = malloc(sizeof(struct CtdlMessage));
1666 memset(msg, 0, sizeof(struct CtdlMessage));
1667 msg->cm_magic = CTDLMESSAGE_MAGIC;
1668 msg->cm_anon_type = MES_NORMAL;
1669 msg->cm_format_type = FMT_RFC822;
1670 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1671 snprintf(msg->cm_fields['M'],
1673 "Content-type: %s\n\n%s\n"
1676 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1677 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1678 CtdlFreeMessage(msg);
1689 * Run through the queue sending out messages.
1691 void smtp_do_queue(void) {
1692 static int doing_queue = 0;
1695 * This is a simple concurrency check to make sure only one queue run
1696 * is done at a time. We could do this with a mutex, but since we
1697 * don't really require extremely fine granularity here, we'll do it
1698 * with a static variable instead.
1700 if (doing_queue) return;
1704 * Go ahead and run the queue
1706 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1708 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1709 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1712 CtdlForEachMessage(MSGS_ALL, 0L, NULL,
1713 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1715 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1722 /*****************************************************************************/
1723 /* SMTP UTILITY COMMANDS */
1724 /*****************************************************************************/
1726 void cmd_smtp(char *argbuf) {
1733 if (CtdlAccessCheck(ac_aide)) return;
1735 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1737 if (!strcasecmp(cmd, "mx")) {
1738 extract_token(node, argbuf, 1, '|', sizeof node);
1739 num_mxhosts = getmx(buf, node);
1740 cprintf("%d %d MX hosts listed for %s\n",
1741 LISTING_FOLLOWS, num_mxhosts, node);
1742 for (i=0; i<num_mxhosts; ++i) {
1743 extract_token(node, buf, i, '|', sizeof node);
1744 cprintf("%s\n", node);
1750 else if (!strcasecmp(cmd, "runqueue")) {
1752 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1757 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1764 * Initialize the SMTP outbound queue
1766 void smtp_init_spoolout(void) {
1767 struct ctdlroom qrbuf;
1770 * Create the room. This will silently fail if the room already
1771 * exists, and that's perfectly ok, because we want it to exist.
1773 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1776 * Make sure it's set to be a "system room" so it doesn't show up
1777 * in the <K>nown rooms list for Aides.
1779 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1780 qrbuf.QRflags2 |= QR2_SYSTEM;
1788 /*****************************************************************************/
1789 /* MODULE INITIALIZATION STUFF */
1790 /*****************************************************************************/
1792 * This cleanup function blows away the temporary memory used by
1795 void smtp_cleanup_function(void) {
1797 /* Don't do this stuff if this is not an SMTP session! */
1798 if (CC->h_command_function != smtp_command_loop) return;
1800 lprintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1808 CTDL_MODULE_INIT(smtp)
1810 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1817 CtdlRegisterServiceHook(config.c_smtps_port,
1824 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1830 CtdlRegisterServiceHook(0, /* local LMTP */
1836 CtdlRegisterServiceHook(0, /* local LMTP */
1837 file_lmtp_unfiltered_socket,
1838 lmtp_unfiltered_greeting,
1842 smtp_init_spoolout();
1843 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1844 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1845 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1847 /* return our Subversion id for the Log */