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
34 #include <sys/types.h>
37 #if TIME_WITH_SYS_TIME
38 # include <sys/time.h>
42 # include <sys/time.h>
52 #include <sys/socket.h>
53 #include <netinet/in.h>
54 #include <arpa/inet.h>
57 #include "sysdep_decls.h"
58 #include "citserver.h"
62 #include "serv_extensions.h"
69 #include "internet_addressing.h"
72 #include "clientsocket.h"
73 #include "locate_host.h"
74 #include "citadel_dirs.h"
77 #include "serv_crypto.h"
86 struct citsmtp { /* Information about the current session */
89 struct ctdluser vrfy_buffer;
94 int number_of_recipients;
96 int message_originated_locally;
102 enum { /* Command states for login authentication */
109 enum { /* Delivery modes */
114 #define SMTP CC->SMTP
115 #define SMTP_RECPS CC->SMTP_RECPS
116 #define SMTP_ROOMS CC->SMTP_ROOMS
119 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
123 /*****************************************************************************/
124 /* SMTP SERVER (INBOUND) STUFF */
125 /*****************************************************************************/
129 * Here's where our SMTP session begins its happy day.
131 void smtp_greeting(void)
133 char message_to_spammer[1024];
135 strcpy(CC->cs_clientname, "SMTP session");
136 CC->internal_pgm = 1;
137 CC->cs_flags |= CS_STEALTH;
138 SMTP = malloc(sizeof(struct citsmtp));
139 SMTP_RECPS = malloc(SIZ);
140 SMTP_ROOMS = malloc(SIZ);
141 memset(SMTP, 0, sizeof(struct citsmtp));
142 memset(SMTP_RECPS, 0, SIZ);
143 memset(SMTP_ROOMS, 0, SIZ);
145 /* If this config option is set, reject connections from problem
146 * addresses immediately instead of after they execute a RCPT
148 if (config.c_rbl_at_greeting) {
149 if (rbl_check(message_to_spammer)) {
150 cprintf("550 %s\r\n", message_to_spammer);
152 /* no need to free(valid), it's not allocated yet */
157 /* Otherwise we're either clean or we check later. */
159 if (CC->nologin==1) {
160 cprintf("500 Too many users are already online (maximum is %d)\r\n",
164 /* no need to free(valid), it's not allocated yet */
168 cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn);
173 * SMTPS is just like SMTP, except it goes crypto right away.
176 void smtps_greeting(void) {
177 CtdlStartTLS(NULL, NULL, NULL);
184 * SMTP MSA port requires authentication.
186 void smtp_msa_greeting(void) {
193 * LMTP is like SMTP but with some extra bonus footage added.
195 void lmtp_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- EXPN\r\n");
299 cprintf("214- HELO\r\n");
300 cprintf("214- HELP\r\n");
301 cprintf("214- MAIL\r\n");
302 cprintf("214- NOOP\r\n");
303 cprintf("214- QUIT\r\n");
304 cprintf("214- RCPT\r\n");
305 cprintf("214- RSET\r\n");
306 cprintf("214- VRFY\r\n");
314 void smtp_get_user(char *argbuf) {
318 CtdlDecodeBase64(username, argbuf, SIZ);
319 /* lprintf(CTDL_DEBUG, "Trying <%s>\n", username); */
320 if (CtdlLoginExistingUser(username) == login_ok) {
321 CtdlEncodeBase64(buf, "Password:", 9);
322 cprintf("334 %s\r\n", buf);
323 SMTP->command_state = smtp_password;
326 cprintf("500 5.7.0 No such user.\r\n");
327 SMTP->command_state = smtp_command;
335 void smtp_get_pass(char *argbuf) {
338 CtdlDecodeBase64(password, argbuf, SIZ);
339 /* lprintf(CTDL_DEBUG, "Trying <%s>\n", password); */
340 if (CtdlTryPassword(password) == pass_ok) {
341 smtp_auth_greeting();
344 cprintf("535 5.7.0 Authentication failed.\r\n");
346 SMTP->command_state = smtp_command;
351 * Back end for PLAIN auth method (either inline or multistate)
353 void smtp_try_plain(char *encoded_authstring) {
354 char decoded_authstring[1024];
359 CtdlDecodeBase64(decoded_authstring,
361 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;
367 if (CtdlLoginExistingUser(user) == login_ok) {
368 if (CtdlTryPassword(pass) == pass_ok) {
369 smtp_auth_greeting();
373 cprintf("504 5.7.4 Authentication failed.\r\n");
378 * Attempt to perform authenticated SMTP
380 void smtp_auth(char *argbuf) {
381 char username_prompt[64];
383 char encoded_authstring[1024];
386 cprintf("504 5.7.4 Already logged in.\r\n");
390 extract_token(method, argbuf, 0, ' ', sizeof method);
392 if (!strncasecmp(method, "login", 5) ) {
393 if (strlen(argbuf) >= 7) {
394 smtp_get_user(&argbuf[6]);
397 CtdlEncodeBase64(username_prompt, "Username:", 9);
398 cprintf("334 %s\r\n", username_prompt);
399 SMTP->command_state = smtp_user;
404 if (!strncasecmp(method, "plain", 5) ) {
405 if (num_tokens(argbuf, ' ') < 2) {
407 SMTP->command_state = smtp_plain;
411 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
413 smtp_try_plain(encoded_authstring);
417 if (strncasecmp(method, "login", 5) ) {
418 cprintf("504 5.7.4 Unknown authentication method.\r\n");
426 * Back end for smtp_vrfy() command
428 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
430 if (!fuzzy_match(us, SMTP->vrfy_match)) {
432 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
438 * Implements the VRFY (verify user name) command.
439 * Performs fuzzy match on full user names.
441 void smtp_vrfy(char *argbuf) {
442 SMTP->vrfy_count = 0;
443 strcpy(SMTP->vrfy_match, argbuf);
444 ForEachUser(smtp_vrfy_backend, NULL);
446 if (SMTP->vrfy_count < 1) {
447 cprintf("550 5.1.1 String does not match anything.\r\n");
449 else if (SMTP->vrfy_count == 1) {
450 cprintf("250 %s <cit%ld@%s>\r\n",
451 SMTP->vrfy_buffer.fullname,
452 SMTP->vrfy_buffer.usernum,
455 else if (SMTP->vrfy_count > 1) {
456 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
465 * Back end for smtp_expn() command
467 void smtp_expn_backend(struct ctdluser *us, void *data) {
469 if (!fuzzy_match(us, SMTP->vrfy_match)) {
471 if (SMTP->vrfy_count >= 1) {
472 cprintf("250-%s <cit%ld@%s>\r\n",
473 SMTP->vrfy_buffer.fullname,
474 SMTP->vrfy_buffer.usernum,
479 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
485 * Implements the EXPN (expand user name) command.
486 * Performs fuzzy match on full user names.
488 void smtp_expn(char *argbuf) {
489 SMTP->vrfy_count = 0;
490 strcpy(SMTP->vrfy_match, argbuf);
491 ForEachUser(smtp_expn_backend, NULL);
493 if (SMTP->vrfy_count < 1) {
494 cprintf("550 5.1.1 String does not match anything.\r\n");
496 else if (SMTP->vrfy_count >= 1) {
497 cprintf("250 %s <cit%ld@%s>\r\n",
498 SMTP->vrfy_buffer.fullname,
499 SMTP->vrfy_buffer.usernum,
506 * Implements the RSET (reset state) command.
507 * Currently this just zeroes out the state buffer. If pointers to data
508 * allocated with malloc() are ever placed in the state buffer, we have to
509 * be sure to free() them first!
511 * Set do_response to nonzero to output the SMTP RSET response code.
513 void smtp_rset(int do_response) {
518 * Our entire SMTP state is discarded when a RSET command is issued,
519 * but we need to preserve this one little piece of information, so
520 * we save it for later.
522 is_lmtp = SMTP->is_lmtp;
523 is_unfiltered = SMTP->is_unfiltered;
525 memset(SMTP, 0, sizeof(struct citsmtp));
528 * It is somewhat ambiguous whether we want to log out when a RSET
529 * command is issued. Here's the code to do it. It is commented out
530 * because some clients (such as Pine) issue RSET commands before
531 * each message, but still expect to be logged in.
533 * if (CC->logged_in) {
539 * Reinstate this little piece of information we saved (see above).
541 SMTP->is_lmtp = is_lmtp;
542 SMTP->is_unfiltered = is_unfiltered;
545 cprintf("250 2.0.0 Zap!\r\n");
550 * Clear out the portions of the state buffer that need to be cleared out
551 * after the DATA command finishes.
553 void smtp_data_clear(void) {
554 strcpy(SMTP->from, "");
555 strcpy(SMTP->recipients, "");
556 SMTP->number_of_recipients = 0;
557 SMTP->delivery_mode = 0;
558 SMTP->message_originated_locally = 0;
564 * Implements the "MAIL From:" command
566 void smtp_mail(char *argbuf) {
571 if (strlen(SMTP->from) != 0) {
572 cprintf("503 5.1.0 Only one sender permitted\r\n");
576 if (strncasecmp(argbuf, "From:", 5)) {
577 cprintf("501 5.1.7 Syntax error\r\n");
581 strcpy(SMTP->from, &argbuf[5]);
583 if (haschar(SMTP->from, '<') > 0) {
584 stripallbut(SMTP->from, '<', '>');
587 /* We used to reject empty sender names, until it was brought to our
588 * attention that RFC1123 5.2.9 requires that this be allowed. So now
589 * we allow it, but replace the empty string with a fake
590 * address so we don't have to contend with the empty string causing
591 * other code to fail when it's expecting something there.
593 if (strlen(SMTP->from) == 0) {
594 strcpy(SMTP->from, "someone@somewhere.org");
597 /* If this SMTP connection is from a logged-in user, force the 'from'
598 * to be the user's Internet e-mail address as Citadel knows it.
601 safestrncpy(SMTP->from, CC->cs_inet_email, sizeof SMTP->from);
602 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
603 SMTP->message_originated_locally = 1;
607 else if (SMTP->is_lmtp) {
608 /* Bypass forgery checking for LMTP */
611 /* Otherwise, make sure outsiders aren't trying to forge mail from
612 * this system (unless, of course, c_allow_spoofing is enabled)
614 else if (config.c_allow_spoofing == 0) {
615 process_rfc822_addr(SMTP->from, user, node, name);
616 if (CtdlHostAlias(node) != hostalias_nomatch) {
618 "You must log in to send mail from %s\r\n",
620 strcpy(SMTP->from, "");
625 cprintf("250 2.0.0 Sender ok\r\n");
631 * Implements the "RCPT To:" command
633 void smtp_rcpt(char *argbuf) {
635 char message_to_spammer[SIZ];
636 struct recptypes *valid = NULL;
638 if (strlen(SMTP->from) == 0) {
639 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
643 if (strncasecmp(argbuf, "To:", 3)) {
644 cprintf("501 5.1.7 Syntax error\r\n");
648 if ( (SMTP->is_msa) && (!CC->logged_in) ) {
650 "You must log in to send mail on this port.\r\n");
651 strcpy(SMTP->from, "");
655 strcpy(recp, &argbuf[3]);
657 stripallbut(recp, '<', '>');
659 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
660 cprintf("452 4.5.3 Too many recipients\r\n");
665 if ( (!CC->logged_in) /* Don't RBL authenticated users */
666 && (!SMTP->is_lmtp) ) { /* Don't RBL LMTP clients */
667 if (config.c_rbl_at_greeting == 0) { /* Don't RBL again if we already did it */
668 if (rbl_check(message_to_spammer)) {
669 cprintf("550 %s\r\n", message_to_spammer);
670 /* no need to free(valid), it's not allocated yet */
676 valid = validate_recipients(recp);
677 if (valid->num_error != 0) {
678 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
683 if (valid->num_internet > 0) {
685 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
686 cprintf("551 5.7.1 <%s> - you do not have permission to send Internet mail\r\n", recp);
693 if (valid->num_internet > 0) {
694 if ( (SMTP->message_originated_locally == 0)
695 && (SMTP->is_lmtp == 0) ) {
696 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
702 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
703 if (strlen(SMTP->recipients) > 0) {
704 strcat(SMTP->recipients, ",");
706 strcat(SMTP->recipients, recp);
707 SMTP->number_of_recipients += 1;
716 * Implements the DATA command
718 void smtp_data(void) {
720 struct CtdlMessage *msg = NULL;
723 struct recptypes *valid;
728 if (strlen(SMTP->from) == 0) {
729 cprintf("503 5.5.1 Need MAIL command first.\r\n");
733 if (SMTP->number_of_recipients < 1) {
734 cprintf("503 5.5.1 Need RCPT command first.\r\n");
738 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
740 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
743 if (body != NULL) snprintf(body, 4096,
744 "Received: from %s (%s [%s])\n"
752 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
755 "Unable to save message: internal error.\r\n");
759 lprintf(CTDL_DEBUG, "Converting message...\n");
760 msg = convert_internet_message(body);
762 /* If the user is locally authenticated, FORCE the From: header to
763 * show up as the real sender. Yes, this violates the RFC standard,
764 * but IT MAKES SENSE. If you prefer strict RFC adherence over
765 * common sense, you can disable this in the configuration.
767 * We also set the "message room name" ('O' field) to MAILROOM
768 * (which is Mail> on most systems) to prevent it from getting set
769 * to something ugly like "0000058008.Sent Items>" when the message
770 * is read with a Citadel client.
772 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
773 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
774 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
775 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
776 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
777 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
778 msg->cm_fields['A'] = strdup(CC->user.fullname);
779 msg->cm_fields['N'] = strdup(config.c_nodename);
780 msg->cm_fields['H'] = strdup(config.c_humannode);
781 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
782 msg->cm_fields['O'] = strdup(MAILROOM);
785 /* Set the "envelope from" address */
786 if (msg->cm_fields['P'] != NULL) {
787 free(msg->cm_fields['P']);
789 msg->cm_fields['P'] = strdup(SMTP->from);
791 /* Set the "envelope to" address */
792 if (msg->cm_fields['V'] != NULL) {
793 free(msg->cm_fields['V']);
795 msg->cm_fields['V'] = strdup(SMTP->recipients);
797 /* Submit the message into the Citadel system. */
798 valid = validate_recipients(SMTP->recipients);
800 /* If there are modules that want to scan this message before final
801 * submission (such as virus checkers or spam filters), call them now
802 * and give them an opportunity to reject the message.
804 if (SMTP->is_unfiltered) {
808 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
811 if (scan_errors > 0) { /* We don't want this message! */
813 if (msg->cm_fields['0'] == NULL) {
814 msg->cm_fields['0'] = strdup(
815 "5.7.1 Message rejected by filter");
818 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
821 else { /* Ok, we'll accept this message. */
822 msgnum = CtdlSubmitMsg(msg, valid, "");
824 sprintf(result, "250 2.0.0 Message accepted.\r\n");
827 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
831 /* For SMTP and ESTMP, just print the result message. For LMTP, we
832 * have to print one result message for each recipient. Since there
833 * is nothing in Citadel which would cause different recipients to
834 * have different results, we can get away with just spitting out the
835 * same message once for each recipient.
838 for (i=0; i<SMTP->number_of_recipients; ++i) {
839 cprintf("%s", result);
843 cprintf("%s", result);
846 /* Write something to the syslog (which may or may not be where the
847 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
850 syslog((LOG_MAIL | LOG_INFO),
851 "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
854 SMTP->number_of_recipients,
862 CtdlFreeMessage(msg);
864 smtp_data_clear(); /* clear out the buffers now */
869 * implements the STARTTLS command (Citadel API version)
872 void smtp_starttls(void)
874 char ok_response[SIZ];
875 char nosup_response[SIZ];
876 char error_response[SIZ];
879 "200 2.0.0 Begin TLS negotiation now\r\n");
880 sprintf(nosup_response,
881 "554 5.7.3 TLS not supported here\r\n");
882 sprintf(error_response,
883 "554 5.7.3 Internal error\r\n");
884 CtdlStartTLS(ok_response, nosup_response, error_response);
892 * Main command loop for SMTP sessions.
894 void smtp_command_loop(void) {
898 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
899 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
900 lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
904 lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
905 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
907 if (SMTP->command_state == smtp_user) {
908 smtp_get_user(cmdbuf);
911 else if (SMTP->command_state == smtp_password) {
912 smtp_get_pass(cmdbuf);
915 else if (SMTP->command_state == smtp_plain) {
916 smtp_try_plain(cmdbuf);
919 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
920 smtp_auth(&cmdbuf[5]);
923 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
927 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
928 smtp_expn(&cmdbuf[5]);
931 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
932 smtp_hello(&cmdbuf[5], 0);
935 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
936 smtp_hello(&cmdbuf[5], 1);
939 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
940 smtp_hello(&cmdbuf[5], 2);
943 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
947 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
948 smtp_mail(&cmdbuf[5]);
951 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
952 cprintf("250 NOOP\r\n");
955 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
956 cprintf("221 Goodbye...\r\n");
961 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
962 smtp_rcpt(&cmdbuf[5]);
965 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
969 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
973 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
974 smtp_vrfy(&cmdbuf[5]);
978 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
987 /*****************************************************************************/
988 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
989 /*****************************************************************************/
996 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
999 void smtp_try(const char *key, const char *addr, int *status,
1000 char *dsn, size_t n, long msgnum)
1007 char user[1024], node[1024], name[1024];
1009 char mailfrom[1024];
1020 /* Parse out the host portion of the recipient address */
1021 process_rfc822_addr(addr, user, node, name);
1023 lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
1026 /* Load the message out of the database */
1027 CC->redirect_buffer = malloc(SIZ);
1028 CC->redirect_len = 0;
1029 CC->redirect_alloc = SIZ;
1030 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
1031 msgtext = CC->redirect_buffer;
1032 msg_size = CC->redirect_len;
1033 CC->redirect_buffer = NULL;
1034 CC->redirect_len = 0;
1035 CC->redirect_alloc = 0;
1037 /* Extract something to send later in the 'MAIL From:' command */
1038 strcpy(mailfrom, "");
1042 if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
1045 if (!strncasecmp(buf, "From:", 5)) {
1046 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
1048 for (i=0; i<strlen(mailfrom); ++i) {
1049 if (!isprint(mailfrom[i])) {
1050 strcpy(&mailfrom[i], &mailfrom[i+1]);
1055 /* Strip out parenthesized names */
1058 for (i=0; i<strlen(mailfrom); ++i) {
1059 if (mailfrom[i] == '(') lp = i;
1060 if (mailfrom[i] == ')') rp = i;
1062 if ((lp>0)&&(rp>lp)) {
1063 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
1066 /* Prefer brokketized names */
1069 for (i=0; i<strlen(mailfrom); ++i) {
1070 if (mailfrom[i] == '<') lp = i;
1071 if (mailfrom[i] == '>') rp = i;
1073 if ( (lp>=0) && (rp>lp) ) {
1075 strcpy(mailfrom, &mailfrom[lp]);
1080 } while (scan_done == 0);
1081 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
1082 stripallbut(mailfrom, '<', '>');
1084 /* Figure out what mail exchanger host we have to connect to */
1085 num_mxhosts = getmx(mxhosts, node);
1086 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
1087 if (num_mxhosts < 1) {
1089 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
1094 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
1096 extract_token(buf, mxhosts, mx, '|', sizeof buf);
1097 strcpy(mx_user, "");
1098 strcpy(mx_pass, "");
1099 if (num_tokens(buf, '@') > 1) {
1100 strcpy (mx_user, buf);
1101 endpart = strrchr(mx_user, '@');
1103 strcpy (mx_host, endpart + 1);
1104 endpart = strrchr(mx_user, ':');
1105 if (endpart != NULL) {
1106 strcpy(mx_pass, endpart+1);
1111 strcpy (mx_host, buf);
1112 endpart = strrchr(mx_host, ':');
1115 strcpy(mx_port, endpart + 1);
1118 strcpy(mx_port, "25");
1120 lprintf(CTDL_DEBUG, "Trying %s : %s ...\n", mx_host, mx_port);
1121 sock = sock_connect(mx_host, mx_port, "tcp");
1122 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
1123 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
1126 snprintf(dsn, SIZ, "%s", strerror(errno));
1129 snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
1135 *status = 4; /* dsn is already filled in */
1139 /* Process the SMTP greeting from the server */
1140 if (ml_sock_gets(sock, buf) < 0) {
1142 strcpy(dsn, "Connection broken during SMTP conversation");
1145 lprintf(CTDL_DEBUG, "<%s\n", buf);
1146 if (buf[0] != '2') {
1147 if (buf[0] == '4') {
1149 safestrncpy(dsn, &buf[4], 1023);
1154 safestrncpy(dsn, &buf[4], 1023);
1159 /* At this point we know we are talking to a real SMTP server */
1161 /* Do a EHLO command. If it fails, try the HELO command. */
1162 snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
1163 lprintf(CTDL_DEBUG, ">%s", buf);
1164 sock_write(sock, buf, strlen(buf));
1165 if (ml_sock_gets(sock, buf) < 0) {
1167 strcpy(dsn, "Connection broken during SMTP HELO");
1170 lprintf(CTDL_DEBUG, "<%s\n", buf);
1171 if (buf[0] != '2') {
1172 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1173 lprintf(CTDL_DEBUG, ">%s", buf);
1174 sock_write(sock, buf, strlen(buf));
1175 if (ml_sock_gets(sock, buf) < 0) {
1177 strcpy(dsn, "Connection broken during SMTP HELO");
1181 if (buf[0] != '2') {
1182 if (buf[0] == '4') {
1184 safestrncpy(dsn, &buf[4], 1023);
1189 safestrncpy(dsn, &buf[4], 1023);
1194 /* Do an AUTH command if necessary */
1195 if (strlen(mx_user) > 0) {
1196 sprintf(buf, "%s%c%s%c%s%c", mx_user, 0, mx_user, 0, mx_pass, 0);
1197 CtdlEncodeBase64(mailfrom, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 3);
1198 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", mailfrom);
1199 lprintf(CTDL_DEBUG, ">%s", buf);
1200 sock_write(sock, buf, strlen(buf));
1201 if (ml_sock_gets(sock, buf) < 0) {
1203 strcpy(dsn, "Connection broken during SMTP AUTH");
1206 lprintf(CTDL_DEBUG, "<%s\n", buf);
1207 if (buf[0] != '2') {
1208 if (buf[0] == '4') {
1210 safestrncpy(dsn, &buf[4], 1023);
1215 safestrncpy(dsn, &buf[4], 1023);
1221 /* previous command succeeded, now try the MAIL From: command */
1222 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1223 lprintf(CTDL_DEBUG, ">%s", buf);
1224 sock_write(sock, buf, strlen(buf));
1225 if (ml_sock_gets(sock, buf) < 0) {
1227 strcpy(dsn, "Connection broken during SMTP MAIL");
1230 lprintf(CTDL_DEBUG, "<%s\n", buf);
1231 if (buf[0] != '2') {
1232 if (buf[0] == '4') {
1234 safestrncpy(dsn, &buf[4], 1023);
1239 safestrncpy(dsn, &buf[4], 1023);
1244 /* MAIL succeeded, now try the RCPT To: command */
1245 snprintf(buf, sizeof buf, "RCPT To: <%s@%s>\r\n", user, node);
1246 lprintf(CTDL_DEBUG, ">%s", buf);
1247 sock_write(sock, buf, strlen(buf));
1248 if (ml_sock_gets(sock, buf) < 0) {
1250 strcpy(dsn, "Connection broken during SMTP RCPT");
1253 lprintf(CTDL_DEBUG, "<%s\n", buf);
1254 if (buf[0] != '2') {
1255 if (buf[0] == '4') {
1257 safestrncpy(dsn, &buf[4], 1023);
1262 safestrncpy(dsn, &buf[4], 1023);
1267 /* RCPT succeeded, now try the DATA command */
1268 lprintf(CTDL_DEBUG, ">DATA\n");
1269 sock_write(sock, "DATA\r\n", 6);
1270 if (ml_sock_gets(sock, buf) < 0) {
1272 strcpy(dsn, "Connection broken during SMTP DATA");
1275 lprintf(CTDL_DEBUG, "<%s\n", buf);
1276 if (buf[0] != '3') {
1277 if (buf[0] == '4') {
1279 safestrncpy(dsn, &buf[4], 1023);
1284 safestrncpy(dsn, &buf[4], 1023);
1289 /* If we reach this point, the server is expecting data */
1290 sock_write(sock, msgtext, msg_size);
1291 if (msgtext[msg_size-1] != 10) {
1292 lprintf(CTDL_WARNING, "Possible problem: message did not "
1293 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1297 sock_write(sock, ".\r\n", 3);
1298 if (ml_sock_gets(sock, buf) < 0) {
1300 strcpy(dsn, "Connection broken during SMTP message transmit");
1303 lprintf(CTDL_DEBUG, "%s\n", buf);
1304 if (buf[0] != '2') {
1305 if (buf[0] == '4') {
1307 safestrncpy(dsn, &buf[4], 1023);
1312 safestrncpy(dsn, &buf[4], 1023);
1318 safestrncpy(dsn, &buf[4], 1023);
1321 lprintf(CTDL_DEBUG, ">QUIT\n");
1322 sock_write(sock, "QUIT\r\n", 6);
1323 ml_sock_gets(sock, buf);
1324 lprintf(CTDL_DEBUG, "<%s\n", buf);
1325 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1328 bail: free(msgtext);
1331 /* Write something to the syslog (which may or may not be where the
1332 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1334 if (enable_syslog) {
1335 syslog((LOG_MAIL | LOG_INFO),
1336 "%ld: to=<%s>, relay=%s, stat=%s",
1350 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1351 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1352 * a "bounce" message (delivery status notification).
1354 void smtp_do_bounce(char *instr) {
1362 char bounceto[1024];
1363 int num_bounces = 0;
1364 int bounce_this = 0;
1365 long bounce_msgid = (-1);
1366 time_t submitted = 0L;
1367 struct CtdlMessage *bmsg = NULL;
1369 struct recptypes *valid;
1370 int successful_bounce = 0;
1372 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1373 strcpy(bounceto, "");
1375 lines = num_tokens(instr, '\n');
1378 /* See if it's time to give up on delivery of this message */
1379 for (i=0; i<lines; ++i) {
1380 extract_token(buf, instr, i, '\n', sizeof buf);
1381 extract_token(key, buf, 0, '|', sizeof key);
1382 extract_token(addr, buf, 1, '|', sizeof addr);
1383 if (!strcasecmp(key, "submitted")) {
1384 submitted = atol(addr);
1388 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1392 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1393 if (bmsg == NULL) return;
1394 memset(bmsg, 0, sizeof(struct CtdlMessage));
1396 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1397 bmsg->cm_anon_type = MES_NORMAL;
1398 bmsg->cm_format_type = 1;
1399 bmsg->cm_fields['A'] = strdup("Citadel");
1400 bmsg->cm_fields['O'] = strdup(MAILROOM);
1401 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1402 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1404 if (give_up) bmsg->cm_fields['M'] = strdup(
1405 "A message you sent could not be delivered to some or all of its recipients\n"
1406 "due to prolonged unavailability of its destination(s).\n"
1407 "Giving up on the following addresses:\n\n"
1410 else bmsg->cm_fields['M'] = strdup(
1411 "A message you sent could not be delivered to some or all of its recipients.\n"
1412 "The following addresses were undeliverable:\n\n"
1416 * Now go through the instructions checking for stuff.
1418 for (i=0; i<lines; ++i) {
1419 extract_token(buf, instr, i, '\n', sizeof buf);
1420 extract_token(key, buf, 0, '|', sizeof key);
1421 extract_token(addr, buf, 1, '|', sizeof addr);
1422 status = extract_int(buf, 2);
1423 extract_token(dsn, buf, 3, '|', sizeof dsn);
1426 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1427 key, addr, status, dsn);
1429 if (!strcasecmp(key, "bounceto")) {
1430 strcpy(bounceto, addr);
1434 (!strcasecmp(key, "local"))
1435 || (!strcasecmp(key, "remote"))
1436 || (!strcasecmp(key, "ignet"))
1437 || (!strcasecmp(key, "room"))
1439 if (status == 5) bounce_this = 1;
1440 if (give_up) bounce_this = 1;
1446 if (bmsg->cm_fields['M'] == NULL) {
1447 lprintf(CTDL_ERR, "ERROR ... M field is null "
1448 "(%s:%d)\n", __FILE__, __LINE__);
1451 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1452 strlen(bmsg->cm_fields['M']) + 1024 );
1453 strcat(bmsg->cm_fields['M'], addr);
1454 strcat(bmsg->cm_fields['M'], ": ");
1455 strcat(bmsg->cm_fields['M'], dsn);
1456 strcat(bmsg->cm_fields['M'], "\n");
1458 remove_token(instr, i, '\n');
1464 /* Deliver the bounce if there's anything worth mentioning */
1465 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1466 if (num_bounces > 0) {
1468 /* First try the user who sent the message */
1469 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1470 if (strlen(bounceto) == 0) {
1471 lprintf(CTDL_ERR, "No bounce address specified\n");
1472 bounce_msgid = (-1L);
1475 /* Can we deliver the bounce to the original sender? */
1476 valid = validate_recipients(bounceto);
1477 if (valid != NULL) {
1478 if (valid->num_error == 0) {
1479 CtdlSubmitMsg(bmsg, valid, "");
1480 successful_bounce = 1;
1484 /* If not, post it in the Aide> room */
1485 if (successful_bounce == 0) {
1486 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1489 /* Free up the memory we used */
1490 if (valid != NULL) {
1495 CtdlFreeMessage(bmsg);
1496 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1501 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1502 * set of delivery instructions for completed deliveries and remove them.
1504 * It returns the number of incomplete deliveries remaining.
1506 int smtp_purge_completed_deliveries(char *instr) {
1517 lines = num_tokens(instr, '\n');
1518 for (i=0; i<lines; ++i) {
1519 extract_token(buf, instr, i, '\n', sizeof buf);
1520 extract_token(key, buf, 0, '|', sizeof key);
1521 extract_token(addr, buf, 1, '|', sizeof addr);
1522 status = extract_int(buf, 2);
1523 extract_token(dsn, buf, 3, '|', sizeof dsn);
1528 (!strcasecmp(key, "local"))
1529 || (!strcasecmp(key, "remote"))
1530 || (!strcasecmp(key, "ignet"))
1531 || (!strcasecmp(key, "room"))
1533 if (status == 2) completed = 1;
1538 remove_token(instr, i, '\n');
1551 * Called by smtp_do_queue() to handle an individual message.
1553 void smtp_do_procmsg(long msgnum, void *userdata) {
1554 struct CtdlMessage *msg = NULL;
1556 char *results = NULL;
1564 long text_msgid = (-1);
1565 int incomplete_deliveries_remaining;
1566 time_t attempted = 0L;
1567 time_t last_attempted = 0L;
1568 time_t retry = SMTP_RETRY_INTERVAL;
1570 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1572 msg = CtdlFetchMessage(msgnum, 1);
1574 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1578 instr = strdup(msg->cm_fields['M']);
1579 CtdlFreeMessage(msg);
1581 /* Strip out the headers amd any other non-instruction line */
1582 lines = num_tokens(instr, '\n');
1583 for (i=0; i<lines; ++i) {
1584 extract_token(buf, instr, i, '\n', sizeof buf);
1585 if (num_tokens(buf, '|') < 2) {
1586 remove_token(instr, i, '\n');
1592 /* Learn the message ID and find out about recent delivery attempts */
1593 lines = num_tokens(instr, '\n');
1594 for (i=0; i<lines; ++i) {
1595 extract_token(buf, instr, i, '\n', sizeof buf);
1596 extract_token(key, buf, 0, '|', sizeof key);
1597 if (!strcasecmp(key, "msgid")) {
1598 text_msgid = extract_long(buf, 1);
1600 if (!strcasecmp(key, "retry")) {
1601 /* double the retry interval after each attempt */
1602 retry = extract_long(buf, 1) * 2L;
1603 if (retry > SMTP_RETRY_MAX) {
1604 retry = SMTP_RETRY_MAX;
1606 remove_token(instr, i, '\n');
1608 if (!strcasecmp(key, "attempted")) {
1609 attempted = extract_long(buf, 1);
1610 if (attempted > last_attempted)
1611 last_attempted = attempted;
1616 * Postpone delivery if we've already tried recently.
1618 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1619 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1626 * Bail out if there's no actual message associated with this
1628 if (text_msgid < 0L) {
1629 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1634 /* Plow through the instructions looking for 'remote' directives and
1635 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1636 * were experienced and it's time to try again)
1638 lines = num_tokens(instr, '\n');
1639 for (i=0; i<lines; ++i) {
1640 extract_token(buf, instr, i, '\n', sizeof buf);
1641 extract_token(key, buf, 0, '|', sizeof key);
1642 extract_token(addr, buf, 1, '|', sizeof addr);
1643 status = extract_int(buf, 2);
1644 extract_token(dsn, buf, 3, '|', sizeof dsn);
1645 if ( (!strcasecmp(key, "remote"))
1646 && ((status==0)||(status==3)||(status==4)) ) {
1648 /* Remove this "remote" instruction from the set,
1649 * but replace the set's final newline if
1650 * remove_token() stripped it. It has to be there.
1652 remove_token(instr, i, '\n');
1653 if (instr[strlen(instr)-1] != '\n') {
1654 strcat(instr, "\n");
1659 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1660 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1662 if (results == NULL) {
1663 results = malloc(1024);
1664 memset(results, 0, 1024);
1667 results = realloc(results,
1668 strlen(results) + 1024);
1670 snprintf(&results[strlen(results)], 1024,
1672 key, addr, status, dsn);
1677 if (results != NULL) {
1678 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1679 strcat(instr, results);
1684 /* Generate 'bounce' messages */
1685 smtp_do_bounce(instr);
1687 /* Go through the delivery list, deleting completed deliveries */
1688 incomplete_deliveries_remaining =
1689 smtp_purge_completed_deliveries(instr);
1693 * No delivery instructions remain, so delete both the instructions
1694 * message and the message message.
1696 if (incomplete_deliveries_remaining <= 0) {
1698 delmsgs[0] = msgnum;
1699 delmsgs[1] = text_msgid;
1700 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
1704 * Uncompleted delivery instructions remain, so delete the old
1705 * instructions and replace with the updated ones.
1707 if (incomplete_deliveries_remaining > 0) {
1708 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
1709 msg = malloc(sizeof(struct CtdlMessage));
1710 memset(msg, 0, sizeof(struct CtdlMessage));
1711 msg->cm_magic = CTDLMESSAGE_MAGIC;
1712 msg->cm_anon_type = MES_NORMAL;
1713 msg->cm_format_type = FMT_RFC822;
1714 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1715 snprintf(msg->cm_fields['M'],
1717 "Content-type: %s\n\n%s\n"
1720 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1721 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1722 CtdlFreeMessage(msg);
1733 * Run through the queue sending out messages.
1735 void smtp_do_queue(void) {
1736 static int doing_queue = 0;
1739 * This is a simple concurrency check to make sure only one queue run
1740 * is done at a time. We could do this with a mutex, but since we
1741 * don't really require extremely fine granularity here, we'll do it
1742 * with a static variable instead.
1744 if (doing_queue) return;
1748 * Go ahead and run the queue
1750 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1752 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1753 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1756 CtdlForEachMessage(MSGS_ALL, 0L, NULL,
1757 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1759 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1766 /*****************************************************************************/
1767 /* SMTP UTILITY COMMANDS */
1768 /*****************************************************************************/
1770 void cmd_smtp(char *argbuf) {
1777 if (CtdlAccessCheck(ac_aide)) return;
1779 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1781 if (!strcasecmp(cmd, "mx")) {
1782 extract_token(node, argbuf, 1, '|', sizeof node);
1783 num_mxhosts = getmx(buf, node);
1784 cprintf("%d %d MX hosts listed for %s\n",
1785 LISTING_FOLLOWS, num_mxhosts, node);
1786 for (i=0; i<num_mxhosts; ++i) {
1787 extract_token(node, buf, i, '|', sizeof node);
1788 cprintf("%s\n", node);
1794 else if (!strcasecmp(cmd, "runqueue")) {
1796 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1801 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1808 * Initialize the SMTP outbound queue
1810 void smtp_init_spoolout(void) {
1811 struct ctdlroom qrbuf;
1814 * Create the room. This will silently fail if the room already
1815 * exists, and that's perfectly ok, because we want it to exist.
1817 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1820 * Make sure it's set to be a "system room" so it doesn't show up
1821 * in the <K>nown rooms list for Aides.
1823 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1824 qrbuf.QRflags2 |= QR2_SYSTEM;
1832 /*****************************************************************************/
1833 /* MODULE INITIALIZATION STUFF */
1834 /*****************************************************************************/
1836 * This cleanup function blows away the temporary memory used by
1839 void smtp_cleanup_function(void) {
1841 /* Don't do this stuff if this is not an SMTP session! */
1842 if (CC->h_command_function != smtp_command_loop) return;
1844 lprintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1854 char *serv_smtp_init(void)
1857 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1864 CtdlRegisterServiceHook(config.c_smtps_port,
1871 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1877 CtdlRegisterServiceHook(0, /* local LMTP */
1883 CtdlRegisterServiceHook(0, /* local LMTP */
1884 file_lmtp_unfiltered_socket,
1885 lmtp_unfiltered_greeting,
1889 smtp_init_spoolout();
1890 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1891 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1892 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");