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 "sysdep_decls.h"
61 #include "citserver.h"
65 #include "serv_extensions.h"
72 #include "internet_addressing.h"
75 #include "clientsocket.h"
76 #include "locate_host.h"
77 #include "citadel_dirs.h"
80 #include "serv_crypto.h"
89 struct citsmtp { /* Information about the current session */
94 int number_of_recipients;
96 int message_originated_locally;
102 enum { /* Command states for login authentication */
109 #define SMTP CC->SMTP
112 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
116 /*****************************************************************************/
117 /* SMTP SERVER (INBOUND) STUFF */
118 /*****************************************************************************/
122 * Here's where our SMTP session begins its happy day.
124 void smtp_greeting(int is_msa)
126 char message_to_spammer[1024];
128 strcpy(CC->cs_clientname, "SMTP session");
129 CC->internal_pgm = 1;
130 CC->cs_flags |= CS_STEALTH;
131 SMTP = malloc(sizeof(struct citsmtp));
132 memset(SMTP, 0, sizeof(struct citsmtp));
133 SMTP->is_msa = is_msa;
135 /* If this config option is set, reject connections from problem
136 * addresses immediately instead of after they execute a RCPT
138 if ( (config.c_rbl_at_greeting) && (SMTP->is_msa == 0) ) {
139 if (rbl_check(message_to_spammer)) {
140 cprintf("550 %s\r\n", message_to_spammer);
142 /* no need to free_recipients(valid), it's not allocated yet */
147 /* Otherwise we're either clean or we check later. */
149 if (CC->nologin==1) {
150 cprintf("500 Too many users are already online (maximum is %d)\r\n",
154 /* no need to free_recipients(valid), it's not allocated yet */
158 /* Note: the FQDN *must* appear as the first thing after the 220 code.
159 * Some clients (including citmail.c) depend on it being there.
161 cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn);
166 * SMTPS is just like SMTP, except it goes crypto right away.
169 void smtps_greeting(void) {
170 CtdlStartTLS(NULL, NULL, NULL);
177 * SMTP MSA port requires authentication.
179 void smtp_msa_greeting(void) {
185 * LMTP is like SMTP but with some extra bonus footage added.
187 void lmtp_greeting(void) {
194 * Generic SMTP MTA greeting
196 void smtp_mta_greeting(void) {
202 * We also have an unfiltered LMTP socket that bypasses spam filters.
204 void lmtp_unfiltered_greeting(void) {
207 SMTP->is_unfiltered = 1;
212 * Login greeting common to all auth methods
214 void smtp_auth_greeting(void) {
215 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
216 lprintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
217 CC->internal_pgm = 0;
218 CC->cs_flags &= ~CS_STEALTH;
223 * Implement HELO and EHLO commands.
225 * which_command: 0=HELO, 1=EHLO, 2=LHLO
227 void smtp_hello(char *argbuf, int which_command) {
229 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
231 if ( (which_command != 2) && (SMTP->is_lmtp) ) {
232 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
236 if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
237 cprintf("500 LHLO is only allowed when running LMTP\r\n");
241 if (which_command == 0) {
242 cprintf("250 Hello %s (%s [%s])\r\n",
249 if (which_command == 1) {
250 cprintf("250-Hello %s (%s [%s])\r\n",
257 cprintf("250-Greetings and joyous salutations.\r\n");
259 cprintf("250-HELP\r\n");
260 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
264 /* Only offer the PIPELINING command if TLS is inactive,
265 * because of flow control issues. Also, avoid offering TLS
266 * if TLS is already active. Finally, we only offer TLS on
267 * the SMTP-MSA port, not on the SMTP-MTA port, due to
268 * questionable reliability of TLS in certain sending MTA's.
270 if ( (!CC->redirect_ssl) && (SMTP->is_msa) ) {
271 cprintf("250-PIPELINING\r\n");
272 cprintf("250-STARTTLS\r\n");
275 #else /* HAVE_OPENSSL */
277 /* Non SSL enabled server, so always offer PIPELINING. */
278 cprintf("250-PIPELINING\r\n");
280 #endif /* HAVE_OPENSSL */
282 cprintf("250-AUTH LOGIN PLAIN\r\n");
283 cprintf("250-AUTH=LOGIN PLAIN\r\n");
285 cprintf("250 ENHANCEDSTATUSCODES\r\n");
292 * Implement HELP command.
294 void smtp_help(void) {
295 cprintf("214-Commands accepted:\r\n");
296 cprintf("214- DATA\r\n");
297 cprintf("214- EHLO\r\n");
298 cprintf("214- HELO\r\n");
299 cprintf("214- HELP\r\n");
300 cprintf("214- MAIL\r\n");
301 cprintf("214- NOOP\r\n");
302 cprintf("214- QUIT\r\n");
303 cprintf("214- RCPT\r\n");
304 cprintf("214- RSET\r\n");
312 void smtp_get_user(char *argbuf) {
316 CtdlDecodeBase64(username, argbuf, SIZ);
317 /* lprintf(CTDL_DEBUG, "Trying <%s>\n", username); */
318 if (CtdlLoginExistingUser(NULL, username) == login_ok) {
319 CtdlEncodeBase64(buf, "Password:", 9);
320 cprintf("334 %s\r\n", buf);
321 SMTP->command_state = smtp_password;
324 cprintf("500 5.7.0 No such user.\r\n");
325 SMTP->command_state = smtp_command;
333 void smtp_get_pass(char *argbuf) {
336 CtdlDecodeBase64(password, argbuf, SIZ);
337 /* lprintf(CTDL_DEBUG, "Trying <%s>\n", password); */
338 if (CtdlTryPassword(password) == pass_ok) {
339 smtp_auth_greeting();
342 cprintf("535 5.7.0 Authentication failed.\r\n");
344 SMTP->command_state = smtp_command;
349 * Back end for PLAIN auth method (either inline or multistate)
351 void smtp_try_plain(char *encoded_authstring) {
352 char decoded_authstring[1024];
358 CtdlDecodeBase64(decoded_authstring, encoded_authstring, strlen(encoded_authstring) );
359 safestrncpy(ident, decoded_authstring, sizeof ident);
360 safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
361 safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
363 SMTP->command_state = smtp_command;
365 if (strlen(ident) > 0) {
366 result = CtdlLoginExistingUser(user, ident);
369 result = CtdlLoginExistingUser(NULL, user);
372 if (result == login_ok) {
373 if (CtdlTryPassword(pass) == pass_ok) {
374 smtp_auth_greeting();
378 cprintf("504 5.7.4 Authentication failed.\r\n");
383 * Attempt to perform authenticated SMTP
385 void smtp_auth(char *argbuf) {
386 char username_prompt[64];
388 char encoded_authstring[1024];
391 cprintf("504 5.7.4 Already logged in.\r\n");
395 extract_token(method, argbuf, 0, ' ', sizeof method);
397 if (!strncasecmp(method, "login", 5) ) {
398 if (strlen(argbuf) >= 7) {
399 smtp_get_user(&argbuf[6]);
402 CtdlEncodeBase64(username_prompt, "Username:", 9);
403 cprintf("334 %s\r\n", username_prompt);
404 SMTP->command_state = smtp_user;
409 if (!strncasecmp(method, "plain", 5) ) {
410 if (num_tokens(argbuf, ' ') < 2) {
412 SMTP->command_state = smtp_plain;
416 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
418 smtp_try_plain(encoded_authstring);
422 if (strncasecmp(method, "login", 5) ) {
423 cprintf("504 5.7.4 Unknown authentication method.\r\n");
431 * Implements the RSET (reset state) command.
432 * Currently this just zeroes out the state buffer. If pointers to data
433 * allocated with malloc() are ever placed in the state buffer, we have to
434 * be sure to free() them first!
436 * Set do_response to nonzero to output the SMTP RSET response code.
438 void smtp_rset(int do_response) {
443 * Our entire SMTP state is discarded when a RSET command is issued,
444 * but we need to preserve this one little piece of information, so
445 * we save it for later.
447 is_lmtp = SMTP->is_lmtp;
448 is_unfiltered = SMTP->is_unfiltered;
450 memset(SMTP, 0, sizeof(struct citsmtp));
453 * It is somewhat ambiguous whether we want to log out when a RSET
454 * command is issued. Here's the code to do it. It is commented out
455 * because some clients (such as Pine) issue RSET commands before
456 * each message, but still expect to be logged in.
458 * if (CC->logged_in) {
464 * Reinstate this little piece of information we saved (see above).
466 SMTP->is_lmtp = is_lmtp;
467 SMTP->is_unfiltered = is_unfiltered;
470 cprintf("250 2.0.0 Zap!\r\n");
475 * Clear out the portions of the state buffer that need to be cleared out
476 * after the DATA command finishes.
478 void smtp_data_clear(void) {
479 strcpy(SMTP->from, "");
480 strcpy(SMTP->recipients, "");
481 SMTP->number_of_recipients = 0;
482 SMTP->delivery_mode = 0;
483 SMTP->message_originated_locally = 0;
489 * Implements the "MAIL From:" command
491 void smtp_mail(char *argbuf) {
496 if (strlen(SMTP->from) != 0) {
497 cprintf("503 5.1.0 Only one sender permitted\r\n");
501 if (strncasecmp(argbuf, "From:", 5)) {
502 cprintf("501 5.1.7 Syntax error\r\n");
506 strcpy(SMTP->from, &argbuf[5]);
508 if (haschar(SMTP->from, '<') > 0) {
509 stripallbut(SMTP->from, '<', '>');
512 /* We used to reject empty sender names, until it was brought to our
513 * attention that RFC1123 5.2.9 requires that this be allowed. So now
514 * we allow it, but replace the empty string with a fake
515 * address so we don't have to contend with the empty string causing
516 * other code to fail when it's expecting something there.
518 if (strlen(SMTP->from) == 0) {
519 strcpy(SMTP->from, "someone@somewhere.org");
522 /* If this SMTP connection is from a logged-in user, force the 'from'
523 * to be the user's Internet e-mail address as Citadel knows it.
526 safestrncpy(SMTP->from, CC->cs_inet_email, sizeof SMTP->from);
527 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
528 SMTP->message_originated_locally = 1;
532 else if (SMTP->is_lmtp) {
533 /* Bypass forgery checking for LMTP */
536 /* Otherwise, make sure outsiders aren't trying to forge mail from
537 * this system (unless, of course, c_allow_spoofing is enabled)
539 else if (config.c_allow_spoofing == 0) {
540 process_rfc822_addr(SMTP->from, user, node, name);
541 if (CtdlHostAlias(node) != hostalias_nomatch) {
543 "You must log in to send mail from %s\r\n",
545 strcpy(SMTP->from, "");
550 cprintf("250 2.0.0 Sender ok\r\n");
556 * Implements the "RCPT To:" command
558 void smtp_rcpt(char *argbuf) {
560 char message_to_spammer[SIZ];
561 struct recptypes *valid = NULL;
563 if (strlen(SMTP->from) == 0) {
564 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
568 if (strncasecmp(argbuf, "To:", 3)) {
569 cprintf("501 5.1.7 Syntax error\r\n");
573 if ( (SMTP->is_msa) && (!CC->logged_in) ) {
575 "You must log in to send mail on this port.\r\n");
576 strcpy(SMTP->from, "");
580 safestrncpy(recp, &argbuf[3], sizeof recp);
582 stripallbut(recp, '<', '>');
584 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
585 cprintf("452 4.5.3 Too many recipients\r\n");
590 if ( (!CC->logged_in) /* Don't RBL authenticated users */
591 && (!SMTP->is_lmtp) ) { /* Don't RBL LMTP clients */
592 if (config.c_rbl_at_greeting == 0) { /* Don't RBL again if we already did it */
593 if (rbl_check(message_to_spammer)) {
594 cprintf("550 %s\r\n", message_to_spammer);
595 /* no need to free_recipients(valid), it's not allocated yet */
601 valid = validate_recipients(recp);
602 if (valid->num_error != 0) {
603 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
604 free_recipients(valid);
608 if (valid->num_internet > 0) {
610 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
611 cprintf("551 5.7.1 <%s> - you do not have permission to send Internet mail\r\n", recp);
612 free_recipients(valid);
618 if (valid->num_internet > 0) {
619 if ( (SMTP->message_originated_locally == 0)
620 && (SMTP->is_lmtp == 0) ) {
621 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
622 free_recipients(valid);
627 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
628 if (strlen(SMTP->recipients) > 0) {
629 strcat(SMTP->recipients, ",");
631 strcat(SMTP->recipients, recp);
632 SMTP->number_of_recipients += 1;
634 free_recipients(valid);
642 * Implements the DATA command
644 void smtp_data(void) {
646 struct CtdlMessage *msg = NULL;
649 struct recptypes *valid;
654 if (strlen(SMTP->from) == 0) {
655 cprintf("503 5.5.1 Need MAIL command first.\r\n");
659 if (SMTP->number_of_recipients < 1) {
660 cprintf("503 5.5.1 Need RCPT command first.\r\n");
664 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
666 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
669 if (body != NULL) snprintf(body, 4096,
670 "Received: from %s (%s [%s])\n"
678 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
681 "Unable to save message: internal error.\r\n");
685 lprintf(CTDL_DEBUG, "Converting message...\n");
686 msg = convert_internet_message(body);
688 /* If the user is locally authenticated, FORCE the From: header to
689 * show up as the real sender. Yes, this violates the RFC standard,
690 * but IT MAKES SENSE. If you prefer strict RFC adherence over
691 * common sense, you can disable this in the configuration.
693 * We also set the "message room name" ('O' field) to MAILROOM
694 * (which is Mail> on most systems) to prevent it from getting set
695 * to something ugly like "0000058008.Sent Items>" when the message
696 * is read with a Citadel client.
698 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
699 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
700 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
701 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
702 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
703 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
704 msg->cm_fields['A'] = strdup(CC->user.fullname);
705 msg->cm_fields['N'] = strdup(config.c_nodename);
706 msg->cm_fields['H'] = strdup(config.c_humannode);
707 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
708 msg->cm_fields['O'] = strdup(MAILROOM);
711 /* Set the "envelope from" address */
712 if (msg->cm_fields['P'] != NULL) {
713 free(msg->cm_fields['P']);
715 msg->cm_fields['P'] = strdup(SMTP->from);
717 /* Set the "envelope to" address */
718 if (msg->cm_fields['V'] != NULL) {
719 free(msg->cm_fields['V']);
721 msg->cm_fields['V'] = strdup(SMTP->recipients);
723 /* Submit the message into the Citadel system. */
724 valid = validate_recipients(SMTP->recipients);
726 /* If there are modules that want to scan this message before final
727 * submission (such as virus checkers or spam filters), call them now
728 * and give them an opportunity to reject the message.
730 if (SMTP->is_unfiltered) {
734 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
737 if (scan_errors > 0) { /* We don't want this message! */
739 if (msg->cm_fields['0'] == NULL) {
740 msg->cm_fields['0'] = strdup(
741 "5.7.1 Message rejected by filter");
744 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
747 else { /* Ok, we'll accept this message. */
748 msgnum = CtdlSubmitMsg(msg, valid, "");
750 sprintf(result, "250 2.0.0 Message accepted.\r\n");
753 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
757 /* For SMTP and ESTMP, just print the result message. For LMTP, we
758 * have to print one result message for each recipient. Since there
759 * is nothing in Citadel which would cause different recipients to
760 * have different results, we can get away with just spitting out the
761 * same message once for each recipient.
764 for (i=0; i<SMTP->number_of_recipients; ++i) {
765 cprintf("%s", result);
769 cprintf("%s", result);
772 /* Write something to the syslog (which may or may not be where the
773 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
776 syslog((LOG_MAIL | LOG_INFO),
777 "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
780 SMTP->number_of_recipients,
788 CtdlFreeMessage(msg);
789 free_recipients(valid);
790 smtp_data_clear(); /* clear out the buffers now */
795 * implements the STARTTLS command (Citadel API version)
798 void smtp_starttls(void)
800 char ok_response[SIZ];
801 char nosup_response[SIZ];
802 char error_response[SIZ];
805 "200 2.0.0 Begin TLS negotiation now\r\n");
806 sprintf(nosup_response,
807 "554 5.7.3 TLS not supported here\r\n");
808 sprintf(error_response,
809 "554 5.7.3 Internal error\r\n");
810 CtdlStartTLS(ok_response, nosup_response, error_response);
818 * Main command loop for SMTP sessions.
820 void smtp_command_loop(void) {
824 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
825 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
826 lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
830 lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
831 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
833 if (SMTP->command_state == smtp_user) {
834 smtp_get_user(cmdbuf);
837 else if (SMTP->command_state == smtp_password) {
838 smtp_get_pass(cmdbuf);
841 else if (SMTP->command_state == smtp_plain) {
842 smtp_try_plain(cmdbuf);
845 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
846 smtp_auth(&cmdbuf[5]);
849 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
853 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
854 smtp_hello(&cmdbuf[5], 0);
857 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
858 smtp_hello(&cmdbuf[5], 1);
861 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
862 smtp_hello(&cmdbuf[5], 2);
865 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
869 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
870 smtp_mail(&cmdbuf[5]);
873 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
874 cprintf("250 NOOP\r\n");
877 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
878 cprintf("221 Goodbye...\r\n");
883 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
884 smtp_rcpt(&cmdbuf[5]);
887 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
891 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
896 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
905 /*****************************************************************************/
906 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
907 /*****************************************************************************/
914 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
917 void smtp_try(const char *key, const char *addr, int *status,
918 char *dsn, size_t n, long msgnum)
925 char user[1024], node[1024], name[1024];
938 /* Parse out the host portion of the recipient address */
939 process_rfc822_addr(addr, user, node, name);
941 lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
944 /* Load the message out of the database */
945 CC->redirect_buffer = malloc(SIZ);
946 CC->redirect_len = 0;
947 CC->redirect_alloc = SIZ;
948 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
949 msgtext = CC->redirect_buffer;
950 msg_size = CC->redirect_len;
951 CC->redirect_buffer = NULL;
952 CC->redirect_len = 0;
953 CC->redirect_alloc = 0;
955 /* Extract something to send later in the 'MAIL From:' command */
956 strcpy(mailfrom, "");
960 if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
963 if (!strncasecmp(buf, "From:", 5)) {
964 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
966 for (i=0; i<strlen(mailfrom); ++i) {
967 if (!isprint(mailfrom[i])) {
968 strcpy(&mailfrom[i], &mailfrom[i+1]);
973 /* Strip out parenthesized names */
976 for (i=0; i<strlen(mailfrom); ++i) {
977 if (mailfrom[i] == '(') lp = i;
978 if (mailfrom[i] == ')') rp = i;
980 if ((lp>0)&&(rp>lp)) {
981 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
984 /* Prefer brokketized names */
987 for (i=0; i<strlen(mailfrom); ++i) {
988 if (mailfrom[i] == '<') lp = i;
989 if (mailfrom[i] == '>') rp = i;
991 if ( (lp>=0) && (rp>lp) ) {
993 strcpy(mailfrom, &mailfrom[lp]);
998 } while (scan_done == 0);
999 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
1000 stripallbut(mailfrom, '<', '>');
1002 /* Figure out what mail exchanger host we have to connect to */
1003 num_mxhosts = getmx(mxhosts, node);
1004 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
1005 if (num_mxhosts < 1) {
1007 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
1012 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
1014 extract_token(buf, mxhosts, mx, '|', sizeof buf);
1015 strcpy(mx_user, "");
1016 strcpy(mx_pass, "");
1017 if (num_tokens(buf, '@') > 1) {
1018 strcpy (mx_user, buf);
1019 endpart = strrchr(mx_user, '@');
1021 strcpy (mx_host, endpart + 1);
1022 endpart = strrchr(mx_user, ':');
1023 if (endpart != NULL) {
1024 strcpy(mx_pass, endpart+1);
1029 strcpy (mx_host, buf);
1030 endpart = strrchr(mx_host, ':');
1033 strcpy(mx_port, endpart + 1);
1036 strcpy(mx_port, "25");
1038 lprintf(CTDL_DEBUG, "Trying %s : %s ...\n", mx_host, mx_port);
1039 sock = sock_connect(mx_host, mx_port, "tcp");
1040 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
1041 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
1044 snprintf(dsn, SIZ, "%s", strerror(errno));
1047 snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
1053 *status = 4; /* dsn is already filled in */
1057 /* Process the SMTP greeting from the server */
1058 if (ml_sock_gets(sock, buf) < 0) {
1060 strcpy(dsn, "Connection broken during SMTP conversation");
1063 lprintf(CTDL_DEBUG, "<%s\n", buf);
1064 if (buf[0] != '2') {
1065 if (buf[0] == '4') {
1067 safestrncpy(dsn, &buf[4], 1023);
1072 safestrncpy(dsn, &buf[4], 1023);
1077 /* At this point we know we are talking to a real SMTP server */
1079 /* Do a EHLO command. If it fails, try the HELO command. */
1080 snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
1081 lprintf(CTDL_DEBUG, ">%s", buf);
1082 sock_write(sock, buf, strlen(buf));
1083 if (ml_sock_gets(sock, buf) < 0) {
1085 strcpy(dsn, "Connection broken during SMTP HELO");
1088 lprintf(CTDL_DEBUG, "<%s\n", buf);
1089 if (buf[0] != '2') {
1090 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1091 lprintf(CTDL_DEBUG, ">%s", buf);
1092 sock_write(sock, buf, strlen(buf));
1093 if (ml_sock_gets(sock, buf) < 0) {
1095 strcpy(dsn, "Connection broken during SMTP HELO");
1099 if (buf[0] != '2') {
1100 if (buf[0] == '4') {
1102 safestrncpy(dsn, &buf[4], 1023);
1107 safestrncpy(dsn, &buf[4], 1023);
1112 /* Do an AUTH command if necessary */
1113 if (strlen(mx_user) > 0) {
1115 sprintf(buf, "%s%c%s%c%s", mx_user, '\0', mx_user, '\0', mx_pass);
1116 CtdlEncodeBase64(encoded, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 2);
1117 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", encoded);
1118 lprintf(CTDL_DEBUG, ">%s", buf);
1119 sock_write(sock, buf, strlen(buf));
1120 if (ml_sock_gets(sock, buf) < 0) {
1122 strcpy(dsn, "Connection broken during SMTP AUTH");
1125 lprintf(CTDL_DEBUG, "<%s\n", buf);
1126 if (buf[0] != '2') {
1127 if (buf[0] == '4') {
1129 safestrncpy(dsn, &buf[4], 1023);
1134 safestrncpy(dsn, &buf[4], 1023);
1140 /* previous command succeeded, now try the MAIL From: command */
1141 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1142 lprintf(CTDL_DEBUG, ">%s", buf);
1143 sock_write(sock, buf, strlen(buf));
1144 if (ml_sock_gets(sock, buf) < 0) {
1146 strcpy(dsn, "Connection broken during SMTP MAIL");
1149 lprintf(CTDL_DEBUG, "<%s\n", buf);
1150 if (buf[0] != '2') {
1151 if (buf[0] == '4') {
1153 safestrncpy(dsn, &buf[4], 1023);
1158 safestrncpy(dsn, &buf[4], 1023);
1163 /* MAIL succeeded, now try the RCPT To: command */
1164 snprintf(buf, sizeof buf, "RCPT To: <%s@%s>\r\n", user, node);
1165 lprintf(CTDL_DEBUG, ">%s", buf);
1166 sock_write(sock, buf, strlen(buf));
1167 if (ml_sock_gets(sock, buf) < 0) {
1169 strcpy(dsn, "Connection broken during SMTP RCPT");
1172 lprintf(CTDL_DEBUG, "<%s\n", buf);
1173 if (buf[0] != '2') {
1174 if (buf[0] == '4') {
1176 safestrncpy(dsn, &buf[4], 1023);
1181 safestrncpy(dsn, &buf[4], 1023);
1186 /* RCPT succeeded, now try the DATA command */
1187 lprintf(CTDL_DEBUG, ">DATA\n");
1188 sock_write(sock, "DATA\r\n", 6);
1189 if (ml_sock_gets(sock, buf) < 0) {
1191 strcpy(dsn, "Connection broken during SMTP DATA");
1194 lprintf(CTDL_DEBUG, "<%s\n", buf);
1195 if (buf[0] != '3') {
1196 if (buf[0] == '4') {
1198 safestrncpy(dsn, &buf[4], 1023);
1203 safestrncpy(dsn, &buf[4], 1023);
1208 /* If we reach this point, the server is expecting data */
1209 sock_write(sock, msgtext, msg_size);
1210 if (msgtext[msg_size-1] != 10) {
1211 lprintf(CTDL_WARNING, "Possible problem: message did not "
1212 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1216 sock_write(sock, ".\r\n", 3);
1217 if (ml_sock_gets(sock, buf) < 0) {
1219 strcpy(dsn, "Connection broken during SMTP message transmit");
1222 lprintf(CTDL_DEBUG, "%s\n", buf);
1223 if (buf[0] != '2') {
1224 if (buf[0] == '4') {
1226 safestrncpy(dsn, &buf[4], 1023);
1231 safestrncpy(dsn, &buf[4], 1023);
1237 safestrncpy(dsn, &buf[4], 1023);
1240 lprintf(CTDL_DEBUG, ">QUIT\n");
1241 sock_write(sock, "QUIT\r\n", 6);
1242 ml_sock_gets(sock, buf);
1243 lprintf(CTDL_DEBUG, "<%s\n", buf);
1244 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1247 bail: free(msgtext);
1250 /* Write something to the syslog (which may or may not be where the
1251 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1253 if (enable_syslog) {
1254 syslog((LOG_MAIL | LOG_INFO),
1255 "%ld: to=<%s>, relay=%s, stat=%s",
1269 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1270 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1271 * a "bounce" message (delivery status notification).
1273 void smtp_do_bounce(char *instr) {
1281 char bounceto[1024];
1283 int num_bounces = 0;
1284 int bounce_this = 0;
1285 long bounce_msgid = (-1);
1286 time_t submitted = 0L;
1287 struct CtdlMessage *bmsg = NULL;
1289 struct recptypes *valid;
1290 int successful_bounce = 0;
1296 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1297 strcpy(bounceto, "");
1298 sprintf(boundary, "=_Citadel_Multipart_%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
1299 lines = num_tokens(instr, '\n');
1301 /* See if it's time to give up on delivery of this message */
1302 for (i=0; i<lines; ++i) {
1303 extract_token(buf, instr, i, '\n', sizeof buf);
1304 extract_token(key, buf, 0, '|', sizeof key);
1305 extract_token(addr, buf, 1, '|', sizeof addr);
1306 if (!strcasecmp(key, "submitted")) {
1307 submitted = atol(addr);
1311 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1315 /* Start building our bounce message */
1317 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1318 if (bmsg == NULL) return;
1319 memset(bmsg, 0, sizeof(struct CtdlMessage));
1321 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1322 bmsg->cm_anon_type = MES_NORMAL;
1323 bmsg->cm_format_type = FMT_RFC822;
1324 bmsg->cm_fields['A'] = strdup("Citadel");
1325 bmsg->cm_fields['O'] = strdup(MAILROOM);
1326 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1327 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1328 bmsg->cm_fields['M'] = malloc(1024);
1330 strcpy(bmsg->cm_fields['M'], "Content-type: multipart/mixed; boundary=\"");
1331 strcat(bmsg->cm_fields['M'], boundary);
1332 strcat(bmsg->cm_fields['M'], "\"\r\n");
1333 strcat(bmsg->cm_fields['M'], "MIME-Version: 1.0\r\n");
1334 strcat(bmsg->cm_fields['M'], "X-Mailer: " CITADEL "\r\n");
1335 strcat(bmsg->cm_fields['M'], "\r\nThis is a multipart message in MIME format.\r\n\r\n");
1336 strcat(bmsg->cm_fields['M'], "--");
1337 strcat(bmsg->cm_fields['M'], boundary);
1338 strcat(bmsg->cm_fields['M'], "\r\n");
1339 strcat(bmsg->cm_fields['M'], "Content-type: text/plain\r\n\r\n");
1341 if (give_up) strcat(bmsg->cm_fields['M'],
1342 "A message you sent could not be delivered to some or all of its recipients\n"
1343 "due to prolonged unavailability of its destination(s).\n"
1344 "Giving up on the following addresses:\n\n"
1347 else strcat(bmsg->cm_fields['M'],
1348 "A message you sent could not be delivered to some or all of its recipients.\n"
1349 "The following addresses were undeliverable:\n\n"
1353 * Now go through the instructions checking for stuff.
1355 for (i=0; i<lines; ++i) {
1356 extract_token(buf, instr, i, '\n', sizeof buf);
1357 extract_token(key, buf, 0, '|', sizeof key);
1358 extract_token(addr, buf, 1, '|', sizeof addr);
1359 status = extract_int(buf, 2);
1360 extract_token(dsn, buf, 3, '|', sizeof dsn);
1363 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1364 key, addr, status, dsn);
1366 if (!strcasecmp(key, "bounceto")) {
1367 strcpy(bounceto, addr);
1370 if (!strcasecmp(key, "msgid")) {
1371 omsgid = atol(addr);
1374 if (!strcasecmp(key, "remote")) {
1375 if (status == 5) bounce_this = 1;
1376 if (give_up) bounce_this = 1;
1382 if (bmsg->cm_fields['M'] == NULL) {
1383 lprintf(CTDL_ERR, "ERROR ... M field is null "
1384 "(%s:%d)\n", __FILE__, __LINE__);
1387 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1388 strlen(bmsg->cm_fields['M']) + 1024 );
1389 strcat(bmsg->cm_fields['M'], addr);
1390 strcat(bmsg->cm_fields['M'], ": ");
1391 strcat(bmsg->cm_fields['M'], dsn);
1392 strcat(bmsg->cm_fields['M'], "\r\n");
1394 remove_token(instr, i, '\n');
1400 /* Attach the original message */
1402 strcat(bmsg->cm_fields['M'], "--");
1403 strcat(bmsg->cm_fields['M'], boundary);
1404 strcat(bmsg->cm_fields['M'], "\r\n");
1405 strcat(bmsg->cm_fields['M'], "Content-type: message/rfc822\r\n");
1406 strcat(bmsg->cm_fields['M'], "Content-Transfer-Encoding: 7bit\r\n");
1407 strcat(bmsg->cm_fields['M'], "Content-Disposition: inline\r\n");
1408 strcat(bmsg->cm_fields['M'], "\r\n");
1410 CC->redirect_buffer = malloc(SIZ);
1411 CC->redirect_len = 0;
1412 CC->redirect_alloc = SIZ;
1413 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
1414 omsgtext = CC->redirect_buffer;
1415 omsgsize = CC->redirect_len;
1416 CC->redirect_buffer = NULL;
1417 CC->redirect_len = 0;
1418 CC->redirect_alloc = 0;
1419 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1420 (strlen(bmsg->cm_fields['M']) + omsgsize + 1024) );
1421 strcat(bmsg->cm_fields['M'], omsgtext);
1425 /* Close the multipart MIME scope */
1426 strcat(bmsg->cm_fields['M'], "--");
1427 strcat(bmsg->cm_fields['M'], boundary);
1428 strcat(bmsg->cm_fields['M'], "--\r\n");
1430 /* Deliver the bounce if there's anything worth mentioning */
1431 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1432 if (num_bounces > 0) {
1434 /* First try the user who sent the message */
1435 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1436 if (strlen(bounceto) == 0) {
1437 lprintf(CTDL_ERR, "No bounce address specified\n");
1438 bounce_msgid = (-1L);
1441 /* Can we deliver the bounce to the original sender? */
1442 valid = validate_recipients(bounceto);
1443 if (valid != NULL) {
1444 if (valid->num_error == 0) {
1445 CtdlSubmitMsg(bmsg, valid, "");
1446 successful_bounce = 1;
1450 /* If not, post it in the Aide> room */
1451 if (successful_bounce == 0) {
1452 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1455 /* Free up the memory we used */
1456 if (valid != NULL) {
1457 free_recipients(valid);
1461 CtdlFreeMessage(bmsg);
1462 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1467 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1468 * set of delivery instructions for completed deliveries and remove them.
1470 * It returns the number of incomplete deliveries remaining.
1472 int smtp_purge_completed_deliveries(char *instr) {
1483 lines = num_tokens(instr, '\n');
1484 for (i=0; i<lines; ++i) {
1485 extract_token(buf, instr, i, '\n', sizeof buf);
1486 extract_token(key, buf, 0, '|', sizeof key);
1487 extract_token(addr, buf, 1, '|', sizeof addr);
1488 status = extract_int(buf, 2);
1489 extract_token(dsn, buf, 3, '|', sizeof dsn);
1493 if (!strcasecmp(key, "remote")) {
1494 if (status == 2) completed = 1;
1499 remove_token(instr, i, '\n');
1512 * Called by smtp_do_queue() to handle an individual message.
1514 void smtp_do_procmsg(long msgnum, void *userdata) {
1515 struct CtdlMessage *msg = NULL;
1517 char *results = NULL;
1525 long text_msgid = (-1);
1526 int incomplete_deliveries_remaining;
1527 time_t attempted = 0L;
1528 time_t last_attempted = 0L;
1529 time_t retry = SMTP_RETRY_INTERVAL;
1531 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1533 msg = CtdlFetchMessage(msgnum, 1);
1535 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1539 instr = strdup(msg->cm_fields['M']);
1540 CtdlFreeMessage(msg);
1542 /* Strip out the headers amd any other non-instruction line */
1543 lines = num_tokens(instr, '\n');
1544 for (i=0; i<lines; ++i) {
1545 extract_token(buf, instr, i, '\n', sizeof buf);
1546 if (num_tokens(buf, '|') < 2) {
1547 remove_token(instr, i, '\n');
1553 /* Learn the message ID and find out about recent delivery attempts */
1554 lines = num_tokens(instr, '\n');
1555 for (i=0; i<lines; ++i) {
1556 extract_token(buf, instr, i, '\n', sizeof buf);
1557 extract_token(key, buf, 0, '|', sizeof key);
1558 if (!strcasecmp(key, "msgid")) {
1559 text_msgid = extract_long(buf, 1);
1561 if (!strcasecmp(key, "retry")) {
1562 /* double the retry interval after each attempt */
1563 retry = extract_long(buf, 1) * 2L;
1564 if (retry > SMTP_RETRY_MAX) {
1565 retry = SMTP_RETRY_MAX;
1567 remove_token(instr, i, '\n');
1569 if (!strcasecmp(key, "attempted")) {
1570 attempted = extract_long(buf, 1);
1571 if (attempted > last_attempted)
1572 last_attempted = attempted;
1577 * Postpone delivery if we've already tried recently.
1579 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1580 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1587 * Bail out if there's no actual message associated with this
1589 if (text_msgid < 0L) {
1590 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1595 /* Plow through the instructions looking for 'remote' directives and
1596 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1597 * were experienced and it's time to try again)
1599 lines = num_tokens(instr, '\n');
1600 for (i=0; i<lines; ++i) {
1601 extract_token(buf, instr, i, '\n', sizeof buf);
1602 extract_token(key, buf, 0, '|', sizeof key);
1603 extract_token(addr, buf, 1, '|', sizeof addr);
1604 status = extract_int(buf, 2);
1605 extract_token(dsn, buf, 3, '|', sizeof dsn);
1606 if ( (!strcasecmp(key, "remote"))
1607 && ((status==0)||(status==3)||(status==4)) ) {
1609 /* Remove this "remote" instruction from the set,
1610 * but replace the set's final newline if
1611 * remove_token() stripped it. It has to be there.
1613 remove_token(instr, i, '\n');
1614 if (instr[strlen(instr)-1] != '\n') {
1615 strcat(instr, "\n");
1620 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1621 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1623 if (results == NULL) {
1624 results = malloc(1024);
1625 memset(results, 0, 1024);
1628 results = realloc(results,
1629 strlen(results) + 1024);
1631 snprintf(&results[strlen(results)], 1024,
1633 key, addr, status, dsn);
1638 if (results != NULL) {
1639 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1640 strcat(instr, results);
1645 /* Generate 'bounce' messages */
1646 smtp_do_bounce(instr);
1648 /* Go through the delivery list, deleting completed deliveries */
1649 incomplete_deliveries_remaining =
1650 smtp_purge_completed_deliveries(instr);
1654 * No delivery instructions remain, so delete both the instructions
1655 * message and the message message.
1657 if (incomplete_deliveries_remaining <= 0) {
1659 delmsgs[0] = msgnum;
1660 delmsgs[1] = text_msgid;
1661 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
1665 * Uncompleted delivery instructions remain, so delete the old
1666 * instructions and replace with the updated ones.
1668 if (incomplete_deliveries_remaining > 0) {
1669 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
1670 msg = malloc(sizeof(struct CtdlMessage));
1671 memset(msg, 0, sizeof(struct CtdlMessage));
1672 msg->cm_magic = CTDLMESSAGE_MAGIC;
1673 msg->cm_anon_type = MES_NORMAL;
1674 msg->cm_format_type = FMT_RFC822;
1675 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1676 snprintf(msg->cm_fields['M'],
1678 "Content-type: %s\n\n%s\n"
1681 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1682 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1683 CtdlFreeMessage(msg);
1694 * Run through the queue sending out messages.
1696 void smtp_do_queue(void) {
1697 static int doing_queue = 0;
1700 * This is a simple concurrency check to make sure only one queue run
1701 * is done at a time. We could do this with a mutex, but since we
1702 * don't really require extremely fine granularity here, we'll do it
1703 * with a static variable instead.
1705 if (doing_queue) return;
1709 * Go ahead and run the queue
1711 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1713 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1714 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1717 CtdlForEachMessage(MSGS_ALL, 0L, NULL,
1718 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1720 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1727 /*****************************************************************************/
1728 /* SMTP UTILITY COMMANDS */
1729 /*****************************************************************************/
1731 void cmd_smtp(char *argbuf) {
1738 if (CtdlAccessCheck(ac_aide)) return;
1740 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1742 if (!strcasecmp(cmd, "mx")) {
1743 extract_token(node, argbuf, 1, '|', sizeof node);
1744 num_mxhosts = getmx(buf, node);
1745 cprintf("%d %d MX hosts listed for %s\n",
1746 LISTING_FOLLOWS, num_mxhosts, node);
1747 for (i=0; i<num_mxhosts; ++i) {
1748 extract_token(node, buf, i, '|', sizeof node);
1749 cprintf("%s\n", node);
1755 else if (!strcasecmp(cmd, "runqueue")) {
1757 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1762 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1769 * Initialize the SMTP outbound queue
1771 void smtp_init_spoolout(void) {
1772 struct ctdlroom qrbuf;
1775 * Create the room. This will silently fail if the room already
1776 * exists, and that's perfectly ok, because we want it to exist.
1778 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1781 * Make sure it's set to be a "system room" so it doesn't show up
1782 * in the <K>nown rooms list for Aides.
1784 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1785 qrbuf.QRflags2 |= QR2_SYSTEM;
1793 /*****************************************************************************/
1794 /* MODULE INITIALIZATION STUFF */
1795 /*****************************************************************************/
1797 * This cleanup function blows away the temporary memory used by
1800 void smtp_cleanup_function(void) {
1802 /* Don't do this stuff if this is not an SMTP session! */
1803 if (CC->h_command_function != smtp_command_loop) return;
1805 lprintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1813 char *serv_smtp_init(void)
1816 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1823 CtdlRegisterServiceHook(config.c_smtps_port,
1830 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1836 CtdlRegisterServiceHook(0, /* local LMTP */
1842 CtdlRegisterServiceHook(0, /* local LMTP */
1843 file_lmtp_unfiltered_socket,
1844 lmtp_unfiltered_greeting,
1848 smtp_init_spoolout();
1849 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1850 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1851 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1853 /* return our Subversion id for the Log */