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"
78 #include "serv_crypto.h"
88 #include "ctdl_module.h"
92 struct citsmtp { /* Information about the current session */
97 int number_of_recipients;
99 int message_originated_locally;
105 enum { /* Command states for login authentication */
112 #define SMTP CC->SMTP
115 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
119 /*****************************************************************************/
120 /* SMTP SERVER (INBOUND) STUFF */
121 /*****************************************************************************/
125 * Here's where our SMTP session begins its happy day.
127 void smtp_greeting(int is_msa)
129 char message_to_spammer[1024];
131 strcpy(CC->cs_clientname, "SMTP session");
132 CC->internal_pgm = 1;
133 CC->cs_flags |= CS_STEALTH;
134 SMTP = malloc(sizeof(struct citsmtp));
135 memset(SMTP, 0, sizeof(struct citsmtp));
136 SMTP->is_msa = is_msa;
138 /* If this config option is set, reject connections from problem
139 * addresses immediately instead of after they execute a RCPT
141 if ( (config.c_rbl_at_greeting) && (SMTP->is_msa == 0) ) {
142 if (rbl_check(message_to_spammer)) {
143 cprintf("550 %s\r\n", message_to_spammer);
145 /* no need to free_recipients(valid), it's not allocated yet */
150 /* Otherwise we're either clean or we check later. */
152 if (CC->nologin==1) {
153 cprintf("500 Too many users are already online (maximum is %d)\r\n",
157 /* no need to free_recipients(valid), it's not allocated yet */
161 /* Note: the FQDN *must* appear as the first thing after the 220 code.
162 * Some clients (including citmail.c) depend on it being there.
164 cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn);
169 * SMTPS is just like SMTP, except it goes crypto right away.
172 void smtps_greeting(void) {
173 CtdlStartTLS(NULL, NULL, NULL);
180 * SMTP MSA port requires authentication.
182 void smtp_msa_greeting(void) {
188 * LMTP is like SMTP but with some extra bonus footage added.
190 void lmtp_greeting(void) {
197 * Generic SMTP MTA greeting
199 void smtp_mta_greeting(void) {
205 * We also have an unfiltered LMTP socket that bypasses spam filters.
207 void lmtp_unfiltered_greeting(void) {
210 SMTP->is_unfiltered = 1;
215 * Login greeting common to all auth methods
217 void smtp_auth_greeting(void) {
218 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
219 lprintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
220 CC->internal_pgm = 0;
221 CC->cs_flags &= ~CS_STEALTH;
226 * Implement HELO and EHLO commands.
228 * which_command: 0=HELO, 1=EHLO, 2=LHLO
230 void smtp_hello(char *argbuf, int which_command) {
232 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
234 if ( (which_command != 2) && (SMTP->is_lmtp) ) {
235 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
239 if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
240 cprintf("500 LHLO is only allowed when running LMTP\r\n");
244 if (which_command == 0) {
245 cprintf("250 Hello %s (%s [%s])\r\n",
252 if (which_command == 1) {
253 cprintf("250-Hello %s (%s [%s])\r\n",
260 cprintf("250-Greetings and joyous salutations.\r\n");
262 cprintf("250-HELP\r\n");
263 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
267 /* Only offer the PIPELINING command if TLS is inactive,
268 * because of flow control issues. Also, avoid offering TLS
269 * if TLS is already active. Finally, we only offer TLS on
270 * the SMTP-MSA port, not on the SMTP-MTA port, due to
271 * questionable reliability of TLS in certain sending MTA's.
273 if ( (!CC->redirect_ssl) && (SMTP->is_msa) ) {
274 cprintf("250-PIPELINING\r\n");
275 cprintf("250-STARTTLS\r\n");
278 #else /* HAVE_OPENSSL */
280 /* Non SSL enabled server, so always offer PIPELINING. */
281 cprintf("250-PIPELINING\r\n");
283 #endif /* HAVE_OPENSSL */
285 cprintf("250-AUTH LOGIN PLAIN\r\n");
286 cprintf("250-AUTH=LOGIN PLAIN\r\n");
288 cprintf("250 ENHANCEDSTATUSCODES\r\n");
295 * Implement HELP command.
297 void smtp_help(void) {
298 cprintf("214-Commands accepted:\r\n");
299 cprintf("214- DATA\r\n");
300 cprintf("214- EHLO\r\n");
301 cprintf("214- HELO\r\n");
302 cprintf("214- HELP\r\n");
303 cprintf("214- MAIL\r\n");
304 cprintf("214- NOOP\r\n");
305 cprintf("214- QUIT\r\n");
306 cprintf("214- RCPT\r\n");
307 cprintf("214- RSET\r\n");
315 void smtp_get_user(char *argbuf) {
319 CtdlDecodeBase64(username, argbuf, SIZ);
320 /* lprintf(CTDL_DEBUG, "Trying <%s>\n", username); */
321 if (CtdlLoginExistingUser(NULL, username) == login_ok) {
322 CtdlEncodeBase64(buf, "Password:", 9);
323 cprintf("334 %s\r\n", buf);
324 SMTP->command_state = smtp_password;
327 cprintf("500 5.7.0 No such user.\r\n");
328 SMTP->command_state = smtp_command;
336 void smtp_get_pass(char *argbuf) {
339 CtdlDecodeBase64(password, argbuf, SIZ);
340 /* lprintf(CTDL_DEBUG, "Trying <%s>\n", password); */
341 if (CtdlTryPassword(password) == pass_ok) {
342 smtp_auth_greeting();
345 cprintf("535 5.7.0 Authentication failed.\r\n");
347 SMTP->command_state = smtp_command;
352 * Back end for PLAIN auth method (either inline or multistate)
354 void smtp_try_plain(char *encoded_authstring) {
355 char decoded_authstring[1024];
361 CtdlDecodeBase64(decoded_authstring, encoded_authstring, strlen(encoded_authstring) );
362 safestrncpy(ident, decoded_authstring, sizeof ident);
363 safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
364 safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
366 SMTP->command_state = smtp_command;
368 if (strlen(ident) > 0) {
369 result = CtdlLoginExistingUser(user, ident);
372 result = CtdlLoginExistingUser(NULL, user);
375 if (result == login_ok) {
376 if (CtdlTryPassword(pass) == pass_ok) {
377 smtp_auth_greeting();
381 cprintf("504 5.7.4 Authentication failed.\r\n");
386 * Attempt to perform authenticated SMTP
388 void smtp_auth(char *argbuf) {
389 char username_prompt[64];
391 char encoded_authstring[1024];
394 cprintf("504 5.7.4 Already logged in.\r\n");
398 extract_token(method, argbuf, 0, ' ', sizeof method);
400 if (!strncasecmp(method, "login", 5) ) {
401 if (strlen(argbuf) >= 7) {
402 smtp_get_user(&argbuf[6]);
405 CtdlEncodeBase64(username_prompt, "Username:", 9);
406 cprintf("334 %s\r\n", username_prompt);
407 SMTP->command_state = smtp_user;
412 if (!strncasecmp(method, "plain", 5) ) {
413 if (num_tokens(argbuf, ' ') < 2) {
415 SMTP->command_state = smtp_plain;
419 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
421 smtp_try_plain(encoded_authstring);
425 if (strncasecmp(method, "login", 5) ) {
426 cprintf("504 5.7.4 Unknown authentication method.\r\n");
434 * Implements the RSET (reset state) command.
435 * Currently this just zeroes out the state buffer. If pointers to data
436 * allocated with malloc() are ever placed in the state buffer, we have to
437 * be sure to free() them first!
439 * Set do_response to nonzero to output the SMTP RSET response code.
441 void smtp_rset(int do_response) {
446 * Our entire SMTP state is discarded when a RSET command is issued,
447 * but we need to preserve this one little piece of information, so
448 * we save it for later.
450 is_lmtp = SMTP->is_lmtp;
451 is_unfiltered = SMTP->is_unfiltered;
453 memset(SMTP, 0, sizeof(struct citsmtp));
456 * It is somewhat ambiguous whether we want to log out when a RSET
457 * command is issued. Here's the code to do it. It is commented out
458 * because some clients (such as Pine) issue RSET commands before
459 * each message, but still expect to be logged in.
461 * if (CC->logged_in) {
467 * Reinstate this little piece of information we saved (see above).
469 SMTP->is_lmtp = is_lmtp;
470 SMTP->is_unfiltered = is_unfiltered;
473 cprintf("250 2.0.0 Zap!\r\n");
478 * Clear out the portions of the state buffer that need to be cleared out
479 * after the DATA command finishes.
481 void smtp_data_clear(void) {
482 strcpy(SMTP->from, "");
483 strcpy(SMTP->recipients, "");
484 SMTP->number_of_recipients = 0;
485 SMTP->delivery_mode = 0;
486 SMTP->message_originated_locally = 0;
492 * Implements the "MAIL From:" command
494 void smtp_mail(char *argbuf) {
499 if (strlen(SMTP->from) != 0) {
500 cprintf("503 5.1.0 Only one sender permitted\r\n");
504 if (strncasecmp(argbuf, "From:", 5)) {
505 cprintf("501 5.1.7 Syntax error\r\n");
509 strcpy(SMTP->from, &argbuf[5]);
511 if (haschar(SMTP->from, '<') > 0) {
512 stripallbut(SMTP->from, '<', '>');
515 /* We used to reject empty sender names, until it was brought to our
516 * attention that RFC1123 5.2.9 requires that this be allowed. So now
517 * we allow it, but replace the empty string with a fake
518 * address so we don't have to contend with the empty string causing
519 * other code to fail when it's expecting something there.
521 if (strlen(SMTP->from) == 0) {
522 strcpy(SMTP->from, "someone@somewhere.org");
525 /* If this SMTP connection is from a logged-in user, force the 'from'
526 * to be the user's Internet e-mail address as Citadel knows it.
529 safestrncpy(SMTP->from, CC->cs_inet_email, sizeof SMTP->from);
530 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
531 SMTP->message_originated_locally = 1;
535 else if (SMTP->is_lmtp) {
536 /* Bypass forgery checking for LMTP */
539 /* Otherwise, make sure outsiders aren't trying to forge mail from
540 * this system (unless, of course, c_allow_spoofing is enabled)
542 else if (config.c_allow_spoofing == 0) {
543 process_rfc822_addr(SMTP->from, user, node, name);
544 if (CtdlHostAlias(node) != hostalias_nomatch) {
546 "You must log in to send mail from %s\r\n",
548 strcpy(SMTP->from, "");
553 cprintf("250 2.0.0 Sender ok\r\n");
559 * Implements the "RCPT To:" command
561 void smtp_rcpt(char *argbuf) {
563 char message_to_spammer[SIZ];
564 struct recptypes *valid = NULL;
566 if (strlen(SMTP->from) == 0) {
567 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
571 if (strncasecmp(argbuf, "To:", 3)) {
572 cprintf("501 5.1.7 Syntax error\r\n");
576 if ( (SMTP->is_msa) && (!CC->logged_in) ) {
578 "You must log in to send mail on this port.\r\n");
579 strcpy(SMTP->from, "");
583 safestrncpy(recp, &argbuf[3], sizeof recp);
585 stripallbut(recp, '<', '>');
587 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
588 cprintf("452 4.5.3 Too many recipients\r\n");
593 if ( (!CC->logged_in) /* Don't RBL authenticated users */
594 && (!SMTP->is_lmtp) ) { /* Don't RBL LMTP clients */
595 if (config.c_rbl_at_greeting == 0) { /* Don't RBL again if we already did it */
596 if (rbl_check(message_to_spammer)) {
597 cprintf("550 %s\r\n", message_to_spammer);
598 /* no need to free_recipients(valid), it's not allocated yet */
604 valid = validate_recipients(recp);
605 if (valid->num_error != 0) {
606 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
607 free_recipients(valid);
611 if (valid->num_internet > 0) {
613 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
614 cprintf("551 5.7.1 <%s> - you do not have permission to send Internet mail\r\n", recp);
615 free_recipients(valid);
621 if (valid->num_internet > 0) {
622 if ( (SMTP->message_originated_locally == 0)
623 && (SMTP->is_lmtp == 0) ) {
624 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
625 free_recipients(valid);
630 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
631 if (strlen(SMTP->recipients) > 0) {
632 strcat(SMTP->recipients, ",");
634 strcat(SMTP->recipients, recp);
635 SMTP->number_of_recipients += 1;
637 free_recipients(valid);
645 * Implements the DATA command
647 void smtp_data(void) {
649 struct CtdlMessage *msg = NULL;
652 struct recptypes *valid;
657 if (strlen(SMTP->from) == 0) {
658 cprintf("503 5.5.1 Need MAIL command first.\r\n");
662 if (SMTP->number_of_recipients < 1) {
663 cprintf("503 5.5.1 Need RCPT command first.\r\n");
667 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
669 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
672 if (body != NULL) snprintf(body, 4096,
673 "Received: from %s (%s [%s])\n"
681 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
684 "Unable to save message: internal error.\r\n");
688 lprintf(CTDL_DEBUG, "Converting message...\n");
689 msg = convert_internet_message(body);
691 /* If the user is locally authenticated, FORCE the From: header to
692 * show up as the real sender. Yes, this violates the RFC standard,
693 * but IT MAKES SENSE. If you prefer strict RFC adherence over
694 * common sense, you can disable this in the configuration.
696 * We also set the "message room name" ('O' field) to MAILROOM
697 * (which is Mail> on most systems) to prevent it from getting set
698 * to something ugly like "0000058008.Sent Items>" when the message
699 * is read with a Citadel client.
701 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
702 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
703 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
704 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
705 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
706 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
707 msg->cm_fields['A'] = strdup(CC->user.fullname);
708 msg->cm_fields['N'] = strdup(config.c_nodename);
709 msg->cm_fields['H'] = strdup(config.c_humannode);
710 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
711 msg->cm_fields['O'] = strdup(MAILROOM);
714 /* Set the "envelope from" address */
715 if (msg->cm_fields['P'] != NULL) {
716 free(msg->cm_fields['P']);
718 msg->cm_fields['P'] = strdup(SMTP->from);
720 /* Set the "envelope to" address */
721 if (msg->cm_fields['V'] != NULL) {
722 free(msg->cm_fields['V']);
724 msg->cm_fields['V'] = strdup(SMTP->recipients);
726 /* Submit the message into the Citadel system. */
727 valid = validate_recipients(SMTP->recipients);
729 /* If there are modules that want to scan this message before final
730 * submission (such as virus checkers or spam filters), call them now
731 * and give them an opportunity to reject the message.
733 if (SMTP->is_unfiltered) {
737 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
740 if (scan_errors > 0) { /* We don't want this message! */
742 if (msg->cm_fields['0'] == NULL) {
743 msg->cm_fields['0'] = strdup(
744 "5.7.1 Message rejected by filter");
747 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
750 else { /* Ok, we'll accept this message. */
751 msgnum = CtdlSubmitMsg(msg, valid, "");
753 sprintf(result, "250 2.0.0 Message accepted.\r\n");
756 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
760 /* For SMTP and ESTMP, just print the result message. For LMTP, we
761 * have to print one result message for each recipient. Since there
762 * is nothing in Citadel which would cause different recipients to
763 * have different results, we can get away with just spitting out the
764 * same message once for each recipient.
767 for (i=0; i<SMTP->number_of_recipients; ++i) {
768 cprintf("%s", result);
772 cprintf("%s", result);
775 /* Write something to the syslog (which may or may not be where the
776 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
779 syslog((LOG_MAIL | LOG_INFO),
780 "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
783 SMTP->number_of_recipients,
791 CtdlFreeMessage(msg);
792 free_recipients(valid);
793 smtp_data_clear(); /* clear out the buffers now */
798 * implements the STARTTLS command (Citadel API version)
801 void smtp_starttls(void)
803 char ok_response[SIZ];
804 char nosup_response[SIZ];
805 char error_response[SIZ];
808 "200 2.0.0 Begin TLS negotiation now\r\n");
809 sprintf(nosup_response,
810 "554 5.7.3 TLS not supported here\r\n");
811 sprintf(error_response,
812 "554 5.7.3 Internal error\r\n");
813 CtdlStartTLS(ok_response, nosup_response, error_response);
821 * Main command loop for SMTP sessions.
823 void smtp_command_loop(void) {
827 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
828 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
829 lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
833 lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
834 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
836 if (SMTP->command_state == smtp_user) {
837 smtp_get_user(cmdbuf);
840 else if (SMTP->command_state == smtp_password) {
841 smtp_get_pass(cmdbuf);
844 else if (SMTP->command_state == smtp_plain) {
845 smtp_try_plain(cmdbuf);
848 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
849 smtp_auth(&cmdbuf[5]);
852 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
856 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
857 smtp_hello(&cmdbuf[5], 0);
860 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
861 smtp_hello(&cmdbuf[5], 1);
864 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
865 smtp_hello(&cmdbuf[5], 2);
868 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
872 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
873 smtp_mail(&cmdbuf[5]);
876 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
877 cprintf("250 NOOP\r\n");
880 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
881 cprintf("221 Goodbye...\r\n");
886 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
887 smtp_rcpt(&cmdbuf[5]);
890 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
894 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
899 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
908 /*****************************************************************************/
909 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
910 /*****************************************************************************/
917 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
920 void smtp_try(const char *key, const char *addr, int *status,
921 char *dsn, size_t n, long msgnum)
928 char user[1024], node[1024], name[1024];
941 /* Parse out the host portion of the recipient address */
942 process_rfc822_addr(addr, user, node, name);
944 lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
947 /* Load the message out of the database */
948 CC->redirect_buffer = malloc(SIZ);
949 CC->redirect_len = 0;
950 CC->redirect_alloc = SIZ;
951 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
952 msgtext = CC->redirect_buffer;
953 msg_size = CC->redirect_len;
954 CC->redirect_buffer = NULL;
955 CC->redirect_len = 0;
956 CC->redirect_alloc = 0;
958 /* Extract something to send later in the 'MAIL From:' command */
959 strcpy(mailfrom, "");
963 if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
966 if (!strncasecmp(buf, "From:", 5)) {
967 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
969 for (i=0; i<strlen(mailfrom); ++i) {
970 if (!isprint(mailfrom[i])) {
971 strcpy(&mailfrom[i], &mailfrom[i+1]);
976 /* Strip out parenthesized names */
979 for (i=0; i<strlen(mailfrom); ++i) {
980 if (mailfrom[i] == '(') lp = i;
981 if (mailfrom[i] == ')') rp = i;
983 if ((lp>0)&&(rp>lp)) {
984 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
987 /* Prefer brokketized names */
990 for (i=0; i<strlen(mailfrom); ++i) {
991 if (mailfrom[i] == '<') lp = i;
992 if (mailfrom[i] == '>') rp = i;
994 if ( (lp>=0) && (rp>lp) ) {
996 strcpy(mailfrom, &mailfrom[lp]);
1001 } while (scan_done == 0);
1002 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
1003 stripallbut(mailfrom, '<', '>');
1005 /* Figure out what mail exchanger host we have to connect to */
1006 num_mxhosts = getmx(mxhosts, node);
1007 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
1008 if (num_mxhosts < 1) {
1010 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
1015 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
1017 extract_token(buf, mxhosts, mx, '|', sizeof buf);
1018 strcpy(mx_user, "");
1019 strcpy(mx_pass, "");
1020 if (num_tokens(buf, '@') > 1) {
1021 strcpy (mx_user, buf);
1022 endpart = strrchr(mx_user, '@');
1024 strcpy (mx_host, endpart + 1);
1025 endpart = strrchr(mx_user, ':');
1026 if (endpart != NULL) {
1027 strcpy(mx_pass, endpart+1);
1032 strcpy (mx_host, buf);
1033 endpart = strrchr(mx_host, ':');
1036 strcpy(mx_port, endpart + 1);
1039 strcpy(mx_port, "25");
1041 lprintf(CTDL_DEBUG, "Trying %s : %s ...\n", mx_host, mx_port);
1042 sock = sock_connect(mx_host, mx_port, "tcp");
1043 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
1044 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
1047 snprintf(dsn, SIZ, "%s", strerror(errno));
1050 snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
1056 *status = 4; /* dsn is already filled in */
1060 /* Process the SMTP greeting from the server */
1061 if (ml_sock_gets(sock, buf) < 0) {
1063 strcpy(dsn, "Connection broken during SMTP conversation");
1066 lprintf(CTDL_DEBUG, "<%s\n", buf);
1067 if (buf[0] != '2') {
1068 if (buf[0] == '4') {
1070 safestrncpy(dsn, &buf[4], 1023);
1075 safestrncpy(dsn, &buf[4], 1023);
1080 /* At this point we know we are talking to a real SMTP server */
1082 /* Do a EHLO command. If it fails, try the HELO command. */
1083 snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
1084 lprintf(CTDL_DEBUG, ">%s", buf);
1085 sock_write(sock, buf, strlen(buf));
1086 if (ml_sock_gets(sock, buf) < 0) {
1088 strcpy(dsn, "Connection broken during SMTP HELO");
1091 lprintf(CTDL_DEBUG, "<%s\n", buf);
1092 if (buf[0] != '2') {
1093 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1094 lprintf(CTDL_DEBUG, ">%s", buf);
1095 sock_write(sock, buf, strlen(buf));
1096 if (ml_sock_gets(sock, buf) < 0) {
1098 strcpy(dsn, "Connection broken during SMTP HELO");
1102 if (buf[0] != '2') {
1103 if (buf[0] == '4') {
1105 safestrncpy(dsn, &buf[4], 1023);
1110 safestrncpy(dsn, &buf[4], 1023);
1115 /* Do an AUTH command if necessary */
1116 if (strlen(mx_user) > 0) {
1118 sprintf(buf, "%s%c%s%c%s", mx_user, '\0', mx_user, '\0', mx_pass);
1119 CtdlEncodeBase64(encoded, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 2);
1120 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", encoded);
1121 lprintf(CTDL_DEBUG, ">%s", buf);
1122 sock_write(sock, buf, strlen(buf));
1123 if (ml_sock_gets(sock, buf) < 0) {
1125 strcpy(dsn, "Connection broken during SMTP AUTH");
1128 lprintf(CTDL_DEBUG, "<%s\n", buf);
1129 if (buf[0] != '2') {
1130 if (buf[0] == '4') {
1132 safestrncpy(dsn, &buf[4], 1023);
1137 safestrncpy(dsn, &buf[4], 1023);
1143 /* previous command succeeded, now try the MAIL From: command */
1144 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1145 lprintf(CTDL_DEBUG, ">%s", buf);
1146 sock_write(sock, buf, strlen(buf));
1147 if (ml_sock_gets(sock, buf) < 0) {
1149 strcpy(dsn, "Connection broken during SMTP MAIL");
1152 lprintf(CTDL_DEBUG, "<%s\n", buf);
1153 if (buf[0] != '2') {
1154 if (buf[0] == '4') {
1156 safestrncpy(dsn, &buf[4], 1023);
1161 safestrncpy(dsn, &buf[4], 1023);
1166 /* MAIL succeeded, now try the RCPT To: command */
1167 snprintf(buf, sizeof buf, "RCPT To: <%s@%s>\r\n", user, node);
1168 lprintf(CTDL_DEBUG, ">%s", buf);
1169 sock_write(sock, buf, strlen(buf));
1170 if (ml_sock_gets(sock, buf) < 0) {
1172 strcpy(dsn, "Connection broken during SMTP RCPT");
1175 lprintf(CTDL_DEBUG, "<%s\n", buf);
1176 if (buf[0] != '2') {
1177 if (buf[0] == '4') {
1179 safestrncpy(dsn, &buf[4], 1023);
1184 safestrncpy(dsn, &buf[4], 1023);
1189 /* RCPT succeeded, now try the DATA command */
1190 lprintf(CTDL_DEBUG, ">DATA\n");
1191 sock_write(sock, "DATA\r\n", 6);
1192 if (ml_sock_gets(sock, buf) < 0) {
1194 strcpy(dsn, "Connection broken during SMTP DATA");
1197 lprintf(CTDL_DEBUG, "<%s\n", buf);
1198 if (buf[0] != '3') {
1199 if (buf[0] == '4') {
1201 safestrncpy(dsn, &buf[4], 1023);
1206 safestrncpy(dsn, &buf[4], 1023);
1211 /* If we reach this point, the server is expecting data */
1212 sock_write(sock, msgtext, msg_size);
1213 if (msgtext[msg_size-1] != 10) {
1214 lprintf(CTDL_WARNING, "Possible problem: message did not "
1215 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1219 sock_write(sock, ".\r\n", 3);
1220 if (ml_sock_gets(sock, buf) < 0) {
1222 strcpy(dsn, "Connection broken during SMTP message transmit");
1225 lprintf(CTDL_DEBUG, "%s\n", buf);
1226 if (buf[0] != '2') {
1227 if (buf[0] == '4') {
1229 safestrncpy(dsn, &buf[4], 1023);
1234 safestrncpy(dsn, &buf[4], 1023);
1240 safestrncpy(dsn, &buf[4], 1023);
1243 lprintf(CTDL_DEBUG, ">QUIT\n");
1244 sock_write(sock, "QUIT\r\n", 6);
1245 ml_sock_gets(sock, buf);
1246 lprintf(CTDL_DEBUG, "<%s\n", buf);
1247 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1250 bail: free(msgtext);
1253 /* Write something to the syslog (which may or may not be where the
1254 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1256 if (enable_syslog) {
1257 syslog((LOG_MAIL | LOG_INFO),
1258 "%ld: to=<%s>, relay=%s, stat=%s",
1272 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1273 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1274 * a "bounce" message (delivery status notification).
1276 void smtp_do_bounce(char *instr) {
1284 char bounceto[1024];
1286 int num_bounces = 0;
1287 int bounce_this = 0;
1288 long bounce_msgid = (-1);
1289 time_t submitted = 0L;
1290 struct CtdlMessage *bmsg = NULL;
1292 struct recptypes *valid;
1293 int successful_bounce = 0;
1299 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1300 strcpy(bounceto, "");
1301 sprintf(boundary, "=_Citadel_Multipart_%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
1302 lines = num_tokens(instr, '\n');
1304 /* See if it's time to give up on delivery of this message */
1305 for (i=0; i<lines; ++i) {
1306 extract_token(buf, instr, i, '\n', sizeof buf);
1307 extract_token(key, buf, 0, '|', sizeof key);
1308 extract_token(addr, buf, 1, '|', sizeof addr);
1309 if (!strcasecmp(key, "submitted")) {
1310 submitted = atol(addr);
1314 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1318 /* Start building our bounce message */
1320 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1321 if (bmsg == NULL) return;
1322 memset(bmsg, 0, sizeof(struct CtdlMessage));
1324 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1325 bmsg->cm_anon_type = MES_NORMAL;
1326 bmsg->cm_format_type = FMT_RFC822;
1327 bmsg->cm_fields['A'] = strdup("Citadel");
1328 bmsg->cm_fields['O'] = strdup(MAILROOM);
1329 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1330 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1331 bmsg->cm_fields['M'] = malloc(1024);
1333 strcpy(bmsg->cm_fields['M'], "Content-type: multipart/mixed; boundary=\"");
1334 strcat(bmsg->cm_fields['M'], boundary);
1335 strcat(bmsg->cm_fields['M'], "\"\r\n");
1336 strcat(bmsg->cm_fields['M'], "MIME-Version: 1.0\r\n");
1337 strcat(bmsg->cm_fields['M'], "X-Mailer: " CITADEL "\r\n");
1338 strcat(bmsg->cm_fields['M'], "\r\nThis is a multipart message in MIME format.\r\n\r\n");
1339 strcat(bmsg->cm_fields['M'], "--");
1340 strcat(bmsg->cm_fields['M'], boundary);
1341 strcat(bmsg->cm_fields['M'], "\r\n");
1342 strcat(bmsg->cm_fields['M'], "Content-type: text/plain\r\n\r\n");
1344 if (give_up) strcat(bmsg->cm_fields['M'],
1345 "A message you sent could not be delivered to some or all of its recipients\n"
1346 "due to prolonged unavailability of its destination(s).\n"
1347 "Giving up on the following addresses:\n\n"
1350 else strcat(bmsg->cm_fields['M'],
1351 "A message you sent could not be delivered to some or all of its recipients.\n"
1352 "The following addresses were undeliverable:\n\n"
1356 * Now go through the instructions checking for stuff.
1358 for (i=0; i<lines; ++i) {
1359 extract_token(buf, instr, i, '\n', sizeof buf);
1360 extract_token(key, buf, 0, '|', sizeof key);
1361 extract_token(addr, buf, 1, '|', sizeof addr);
1362 status = extract_int(buf, 2);
1363 extract_token(dsn, buf, 3, '|', sizeof dsn);
1366 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1367 key, addr, status, dsn);
1369 if (!strcasecmp(key, "bounceto")) {
1370 strcpy(bounceto, addr);
1373 if (!strcasecmp(key, "msgid")) {
1374 omsgid = atol(addr);
1377 if (!strcasecmp(key, "remote")) {
1378 if (status == 5) bounce_this = 1;
1379 if (give_up) bounce_this = 1;
1385 if (bmsg->cm_fields['M'] == NULL) {
1386 lprintf(CTDL_ERR, "ERROR ... M field is null "
1387 "(%s:%d)\n", __FILE__, __LINE__);
1390 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1391 strlen(bmsg->cm_fields['M']) + 1024 );
1392 strcat(bmsg->cm_fields['M'], addr);
1393 strcat(bmsg->cm_fields['M'], ": ");
1394 strcat(bmsg->cm_fields['M'], dsn);
1395 strcat(bmsg->cm_fields['M'], "\r\n");
1397 remove_token(instr, i, '\n');
1403 /* Attach the original message */
1405 strcat(bmsg->cm_fields['M'], "--");
1406 strcat(bmsg->cm_fields['M'], boundary);
1407 strcat(bmsg->cm_fields['M'], "\r\n");
1408 strcat(bmsg->cm_fields['M'], "Content-type: message/rfc822\r\n");
1409 strcat(bmsg->cm_fields['M'], "Content-Transfer-Encoding: 7bit\r\n");
1410 strcat(bmsg->cm_fields['M'], "Content-Disposition: inline\r\n");
1411 strcat(bmsg->cm_fields['M'], "\r\n");
1413 CC->redirect_buffer = malloc(SIZ);
1414 CC->redirect_len = 0;
1415 CC->redirect_alloc = SIZ;
1416 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
1417 omsgtext = CC->redirect_buffer;
1418 omsgsize = CC->redirect_len;
1419 CC->redirect_buffer = NULL;
1420 CC->redirect_len = 0;
1421 CC->redirect_alloc = 0;
1422 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1423 (strlen(bmsg->cm_fields['M']) + omsgsize + 1024) );
1424 strcat(bmsg->cm_fields['M'], omsgtext);
1428 /* Close the multipart MIME scope */
1429 strcat(bmsg->cm_fields['M'], "--");
1430 strcat(bmsg->cm_fields['M'], boundary);
1431 strcat(bmsg->cm_fields['M'], "--\r\n");
1433 /* Deliver the bounce if there's anything worth mentioning */
1434 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1435 if (num_bounces > 0) {
1437 /* First try the user who sent the message */
1438 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1439 if (strlen(bounceto) == 0) {
1440 lprintf(CTDL_ERR, "No bounce address specified\n");
1441 bounce_msgid = (-1L);
1444 /* Can we deliver the bounce to the original sender? */
1445 valid = validate_recipients(bounceto);
1446 if (valid != NULL) {
1447 if (valid->num_error == 0) {
1448 CtdlSubmitMsg(bmsg, valid, "");
1449 successful_bounce = 1;
1453 /* If not, post it in the Aide> room */
1454 if (successful_bounce == 0) {
1455 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1458 /* Free up the memory we used */
1459 if (valid != NULL) {
1460 free_recipients(valid);
1464 CtdlFreeMessage(bmsg);
1465 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1470 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1471 * set of delivery instructions for completed deliveries and remove them.
1473 * It returns the number of incomplete deliveries remaining.
1475 int smtp_purge_completed_deliveries(char *instr) {
1486 lines = num_tokens(instr, '\n');
1487 for (i=0; i<lines; ++i) {
1488 extract_token(buf, instr, i, '\n', sizeof buf);
1489 extract_token(key, buf, 0, '|', sizeof key);
1490 extract_token(addr, buf, 1, '|', sizeof addr);
1491 status = extract_int(buf, 2);
1492 extract_token(dsn, buf, 3, '|', sizeof dsn);
1496 if (!strcasecmp(key, "remote")) {
1497 if (status == 2) completed = 1;
1502 remove_token(instr, i, '\n');
1515 * Called by smtp_do_queue() to handle an individual message.
1517 void smtp_do_procmsg(long msgnum, void *userdata) {
1518 struct CtdlMessage *msg = NULL;
1520 char *results = NULL;
1528 long text_msgid = (-1);
1529 int incomplete_deliveries_remaining;
1530 time_t attempted = 0L;
1531 time_t last_attempted = 0L;
1532 time_t retry = SMTP_RETRY_INTERVAL;
1534 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1536 msg = CtdlFetchMessage(msgnum, 1);
1538 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1542 instr = strdup(msg->cm_fields['M']);
1543 CtdlFreeMessage(msg);
1545 /* Strip out the headers amd any other non-instruction line */
1546 lines = num_tokens(instr, '\n');
1547 for (i=0; i<lines; ++i) {
1548 extract_token(buf, instr, i, '\n', sizeof buf);
1549 if (num_tokens(buf, '|') < 2) {
1550 remove_token(instr, i, '\n');
1556 /* Learn the message ID and find out about recent delivery attempts */
1557 lines = num_tokens(instr, '\n');
1558 for (i=0; i<lines; ++i) {
1559 extract_token(buf, instr, i, '\n', sizeof buf);
1560 extract_token(key, buf, 0, '|', sizeof key);
1561 if (!strcasecmp(key, "msgid")) {
1562 text_msgid = extract_long(buf, 1);
1564 if (!strcasecmp(key, "retry")) {
1565 /* double the retry interval after each attempt */
1566 retry = extract_long(buf, 1) * 2L;
1567 if (retry > SMTP_RETRY_MAX) {
1568 retry = SMTP_RETRY_MAX;
1570 remove_token(instr, i, '\n');
1572 if (!strcasecmp(key, "attempted")) {
1573 attempted = extract_long(buf, 1);
1574 if (attempted > last_attempted)
1575 last_attempted = attempted;
1580 * Postpone delivery if we've already tried recently.
1582 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1583 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1590 * Bail out if there's no actual message associated with this
1592 if (text_msgid < 0L) {
1593 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1598 /* Plow through the instructions looking for 'remote' directives and
1599 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1600 * were experienced and it's time to try again)
1602 lines = num_tokens(instr, '\n');
1603 for (i=0; i<lines; ++i) {
1604 extract_token(buf, instr, i, '\n', sizeof buf);
1605 extract_token(key, buf, 0, '|', sizeof key);
1606 extract_token(addr, buf, 1, '|', sizeof addr);
1607 status = extract_int(buf, 2);
1608 extract_token(dsn, buf, 3, '|', sizeof dsn);
1609 if ( (!strcasecmp(key, "remote"))
1610 && ((status==0)||(status==3)||(status==4)) ) {
1612 /* Remove this "remote" instruction from the set,
1613 * but replace the set's final newline if
1614 * remove_token() stripped it. It has to be there.
1616 remove_token(instr, i, '\n');
1617 if (instr[strlen(instr)-1] != '\n') {
1618 strcat(instr, "\n");
1623 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1624 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1626 if (results == NULL) {
1627 results = malloc(1024);
1628 memset(results, 0, 1024);
1631 results = realloc(results,
1632 strlen(results) + 1024);
1634 snprintf(&results[strlen(results)], 1024,
1636 key, addr, status, dsn);
1641 if (results != NULL) {
1642 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1643 strcat(instr, results);
1648 /* Generate 'bounce' messages */
1649 smtp_do_bounce(instr);
1651 /* Go through the delivery list, deleting completed deliveries */
1652 incomplete_deliveries_remaining =
1653 smtp_purge_completed_deliveries(instr);
1657 * No delivery instructions remain, so delete both the instructions
1658 * message and the message message.
1660 if (incomplete_deliveries_remaining <= 0) {
1662 delmsgs[0] = msgnum;
1663 delmsgs[1] = text_msgid;
1664 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
1668 * Uncompleted delivery instructions remain, so delete the old
1669 * instructions and replace with the updated ones.
1671 if (incomplete_deliveries_remaining > 0) {
1672 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
1673 msg = malloc(sizeof(struct CtdlMessage));
1674 memset(msg, 0, sizeof(struct CtdlMessage));
1675 msg->cm_magic = CTDLMESSAGE_MAGIC;
1676 msg->cm_anon_type = MES_NORMAL;
1677 msg->cm_format_type = FMT_RFC822;
1678 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1679 snprintf(msg->cm_fields['M'],
1681 "Content-type: %s\n\n%s\n"
1684 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1685 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1686 CtdlFreeMessage(msg);
1697 * Run through the queue sending out messages.
1699 void smtp_do_queue(void) {
1700 static int doing_queue = 0;
1703 * This is a simple concurrency check to make sure only one queue run
1704 * is done at a time. We could do this with a mutex, but since we
1705 * don't really require extremely fine granularity here, we'll do it
1706 * with a static variable instead.
1708 if (doing_queue) return;
1712 * Go ahead and run the queue
1714 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1716 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1717 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1720 CtdlForEachMessage(MSGS_ALL, 0L, NULL,
1721 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1723 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1730 /*****************************************************************************/
1731 /* SMTP UTILITY COMMANDS */
1732 /*****************************************************************************/
1734 void cmd_smtp(char *argbuf) {
1741 if (CtdlAccessCheck(ac_aide)) return;
1743 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1745 if (!strcasecmp(cmd, "mx")) {
1746 extract_token(node, argbuf, 1, '|', sizeof node);
1747 num_mxhosts = getmx(buf, node);
1748 cprintf("%d %d MX hosts listed for %s\n",
1749 LISTING_FOLLOWS, num_mxhosts, node);
1750 for (i=0; i<num_mxhosts; ++i) {
1751 extract_token(node, buf, i, '|', sizeof node);
1752 cprintf("%s\n", node);
1758 else if (!strcasecmp(cmd, "runqueue")) {
1760 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1765 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1772 * Initialize the SMTP outbound queue
1774 void smtp_init_spoolout(void) {
1775 struct ctdlroom qrbuf;
1778 * Create the room. This will silently fail if the room already
1779 * exists, and that's perfectly ok, because we want it to exist.
1781 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1784 * Make sure it's set to be a "system room" so it doesn't show up
1785 * in the <K>nown rooms list for Aides.
1787 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1788 qrbuf.QRflags2 |= QR2_SYSTEM;
1796 /*****************************************************************************/
1797 /* MODULE INITIALIZATION STUFF */
1798 /*****************************************************************************/
1800 * This cleanup function blows away the temporary memory used by
1803 void smtp_cleanup_function(void) {
1805 /* Don't do this stuff if this is not an SMTP session! */
1806 if (CC->h_command_function != smtp_command_loop) return;
1808 lprintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1816 CTDL_MODULE_INIT(smtp)
1818 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1825 CtdlRegisterServiceHook(config.c_smtps_port,
1832 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1838 CtdlRegisterServiceHook(0, /* local LMTP */
1844 CtdlRegisterServiceHook(0, /* local LMTP */
1845 file_lmtp_unfiltered_socket,
1846 lmtp_unfiltered_greeting,
1850 smtp_init_spoolout();
1851 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1852 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1853 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1855 /* return our Subversion id for the Log */