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. */
158 cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn);
163 * SMTPS is just like SMTP, except it goes crypto right away.
166 void smtps_greeting(void) {
167 CtdlStartTLS(NULL, NULL, NULL);
174 * SMTP MSA port requires authentication.
176 void smtp_msa_greeting(void) {
183 * LMTP is like SMTP but with some extra bonus footage added.
185 void lmtp_greeting(void) {
192 * We also have an unfiltered LMTP socket that bypasses spam filters.
194 void lmtp_unfiltered_greeting(void) {
197 SMTP->is_unfiltered = 1;
202 * Login greeting common to all auth methods
204 void smtp_auth_greeting(void) {
205 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
206 lprintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
207 CC->internal_pgm = 0;
208 CC->cs_flags &= ~CS_STEALTH;
213 * Implement HELO and EHLO commands.
215 * which_command: 0=HELO, 1=EHLO, 2=LHLO
217 void smtp_hello(char *argbuf, int which_command) {
219 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
221 if ( (which_command != 2) && (SMTP->is_lmtp) ) {
222 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
226 if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
227 cprintf("500 LHLO is only allowed when running LMTP\r\n");
231 if (which_command == 0) {
232 cprintf("250 Hello %s (%s [%s])\r\n",
239 if (which_command == 1) {
240 cprintf("250-Hello %s (%s [%s])\r\n",
247 cprintf("250-Greetings and joyous salutations.\r\n");
249 cprintf("250-HELP\r\n");
250 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
254 /* Only offer the PIPELINING command if TLS is inactive,
255 * because of flow control issues. Also, avoid offering TLS
256 * if TLS is already active. Finally, we only offer TLS on
257 * the SMTP-MSA port, not on the SMTP-MTA port, due to
258 * questionable reliability of TLS in certain sending MTA's.
260 if ( (!CC->redirect_ssl) && (SMTP->is_msa) ) {
261 cprintf("250-PIPELINING\r\n");
262 cprintf("250-STARTTLS\r\n");
265 #else /* HAVE_OPENSSL */
267 /* Non SSL enabled server, so always offer PIPELINING. */
268 cprintf("250-PIPELINING\r\n");
270 #endif /* HAVE_OPENSSL */
272 cprintf("250-AUTH LOGIN PLAIN\r\n");
273 cprintf("250-AUTH=LOGIN PLAIN\r\n");
275 cprintf("250 ENHANCEDSTATUSCODES\r\n");
282 * Implement HELP command.
284 void smtp_help(void) {
285 cprintf("214-Commands accepted:\r\n");
286 cprintf("214- DATA\r\n");
287 cprintf("214- EHLO\r\n");
288 cprintf("214- EXPN\r\n");
289 cprintf("214- HELO\r\n");
290 cprintf("214- HELP\r\n");
291 cprintf("214- MAIL\r\n");
292 cprintf("214- NOOP\r\n");
293 cprintf("214- QUIT\r\n");
294 cprintf("214- RCPT\r\n");
295 cprintf("214- RSET\r\n");
296 cprintf("214- VRFY\r\n");
304 void smtp_get_user(char *argbuf) {
308 CtdlDecodeBase64(username, argbuf, SIZ);
309 /* lprintf(CTDL_DEBUG, "Trying <%s>\n", username); */
310 if (CtdlLoginExistingUser(username) == login_ok) {
311 CtdlEncodeBase64(buf, "Password:", 9);
312 cprintf("334 %s\r\n", buf);
313 SMTP->command_state = smtp_password;
316 cprintf("500 5.7.0 No such user.\r\n");
317 SMTP->command_state = smtp_command;
325 void smtp_get_pass(char *argbuf) {
328 CtdlDecodeBase64(password, argbuf, SIZ);
329 /* lprintf(CTDL_DEBUG, "Trying <%s>\n", password); */
330 if (CtdlTryPassword(password) == pass_ok) {
331 smtp_auth_greeting();
334 cprintf("535 5.7.0 Authentication failed.\r\n");
336 SMTP->command_state = smtp_command;
341 * Back end for PLAIN auth method (either inline or multistate)
343 void smtp_try_plain(char *encoded_authstring) {
344 char decoded_authstring[1024];
349 CtdlDecodeBase64(decoded_authstring,
351 strlen(encoded_authstring) );
352 safestrncpy(ident, decoded_authstring, sizeof ident);
353 safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
354 safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
356 SMTP->command_state = smtp_command;
357 if (CtdlLoginExistingUser(user) == login_ok) {
358 if (CtdlTryPassword(pass) == pass_ok) {
359 smtp_auth_greeting();
363 cprintf("504 5.7.4 Authentication failed.\r\n");
368 * Attempt to perform authenticated SMTP
370 void smtp_auth(char *argbuf) {
371 char username_prompt[64];
373 char encoded_authstring[1024];
376 cprintf("504 5.7.4 Already logged in.\r\n");
380 extract_token(method, argbuf, 0, ' ', sizeof method);
382 if (!strncasecmp(method, "login", 5) ) {
383 if (strlen(argbuf) >= 7) {
384 smtp_get_user(&argbuf[6]);
387 CtdlEncodeBase64(username_prompt, "Username:", 9);
388 cprintf("334 %s\r\n", username_prompt);
389 SMTP->command_state = smtp_user;
394 if (!strncasecmp(method, "plain", 5) ) {
395 if (num_tokens(argbuf, ' ') < 2) {
397 SMTP->command_state = smtp_plain;
401 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
403 smtp_try_plain(encoded_authstring);
407 if (strncasecmp(method, "login", 5) ) {
408 cprintf("504 5.7.4 Unknown authentication method.\r\n");
416 * Back end for smtp_vrfy() command
418 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
420 if (!fuzzy_match(us, SMTP->vrfy_match)) {
422 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
428 * Implements the VRFY (verify user name) command.
429 * Performs fuzzy match on full user names.
431 void smtp_vrfy(char *argbuf) {
432 SMTP->vrfy_count = 0;
433 strcpy(SMTP->vrfy_match, argbuf);
434 ForEachUser(smtp_vrfy_backend, NULL);
436 if (SMTP->vrfy_count < 1) {
437 cprintf("550 5.1.1 String does not match anything.\r\n");
439 else if (SMTP->vrfy_count == 1) {
440 cprintf("250 %s <cit%ld@%s>\r\n",
441 SMTP->vrfy_buffer.fullname,
442 SMTP->vrfy_buffer.usernum,
445 else if (SMTP->vrfy_count > 1) {
446 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
455 * Back end for smtp_expn() command
457 void smtp_expn_backend(struct ctdluser *us, void *data) {
459 if (!fuzzy_match(us, SMTP->vrfy_match)) {
461 if (SMTP->vrfy_count >= 1) {
462 cprintf("250-%s <cit%ld@%s>\r\n",
463 SMTP->vrfy_buffer.fullname,
464 SMTP->vrfy_buffer.usernum,
469 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
475 * Implements the EXPN (expand user name) command.
476 * Performs fuzzy match on full user names.
478 void smtp_expn(char *argbuf) {
479 SMTP->vrfy_count = 0;
480 strcpy(SMTP->vrfy_match, argbuf);
481 ForEachUser(smtp_expn_backend, NULL);
483 if (SMTP->vrfy_count < 1) {
484 cprintf("550 5.1.1 String does not match anything.\r\n");
486 else if (SMTP->vrfy_count >= 1) {
487 cprintf("250 %s <cit%ld@%s>\r\n",
488 SMTP->vrfy_buffer.fullname,
489 SMTP->vrfy_buffer.usernum,
496 * Implements the RSET (reset state) command.
497 * Currently this just zeroes out the state buffer. If pointers to data
498 * allocated with malloc() are ever placed in the state buffer, we have to
499 * be sure to free() them first!
501 * Set do_response to nonzero to output the SMTP RSET response code.
503 void smtp_rset(int do_response) {
508 * Our entire SMTP state is discarded when a RSET command is issued,
509 * but we need to preserve this one little piece of information, so
510 * we save it for later.
512 is_lmtp = SMTP->is_lmtp;
513 is_unfiltered = SMTP->is_unfiltered;
515 memset(SMTP, 0, sizeof(struct citsmtp));
518 * It is somewhat ambiguous whether we want to log out when a RSET
519 * command is issued. Here's the code to do it. It is commented out
520 * because some clients (such as Pine) issue RSET commands before
521 * each message, but still expect to be logged in.
523 * if (CC->logged_in) {
529 * Reinstate this little piece of information we saved (see above).
531 SMTP->is_lmtp = is_lmtp;
532 SMTP->is_unfiltered = is_unfiltered;
535 cprintf("250 2.0.0 Zap!\r\n");
540 * Clear out the portions of the state buffer that need to be cleared out
541 * after the DATA command finishes.
543 void smtp_data_clear(void) {
544 strcpy(SMTP->from, "");
545 strcpy(SMTP->recipients, "");
546 SMTP->number_of_recipients = 0;
547 SMTP->delivery_mode = 0;
548 SMTP->message_originated_locally = 0;
554 * Implements the "MAIL From:" command
556 void smtp_mail(char *argbuf) {
561 if (strlen(SMTP->from) != 0) {
562 cprintf("503 5.1.0 Only one sender permitted\r\n");
566 if (strncasecmp(argbuf, "From:", 5)) {
567 cprintf("501 5.1.7 Syntax error\r\n");
571 strcpy(SMTP->from, &argbuf[5]);
573 if (haschar(SMTP->from, '<') > 0) {
574 stripallbut(SMTP->from, '<', '>');
577 /* We used to reject empty sender names, until it was brought to our
578 * attention that RFC1123 5.2.9 requires that this be allowed. So now
579 * we allow it, but replace the empty string with a fake
580 * address so we don't have to contend with the empty string causing
581 * other code to fail when it's expecting something there.
583 if (strlen(SMTP->from) == 0) {
584 strcpy(SMTP->from, "someone@somewhere.org");
587 /* If this SMTP connection is from a logged-in user, force the 'from'
588 * to be the user's Internet e-mail address as Citadel knows it.
591 safestrncpy(SMTP->from, CC->cs_inet_email, sizeof SMTP->from);
592 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
593 SMTP->message_originated_locally = 1;
597 else if (SMTP->is_lmtp) {
598 /* Bypass forgery checking for LMTP */
601 /* Otherwise, make sure outsiders aren't trying to forge mail from
602 * this system (unless, of course, c_allow_spoofing is enabled)
604 else if (config.c_allow_spoofing == 0) {
605 process_rfc822_addr(SMTP->from, user, node, name);
606 if (CtdlHostAlias(node) != hostalias_nomatch) {
608 "You must log in to send mail from %s\r\n",
610 strcpy(SMTP->from, "");
615 cprintf("250 2.0.0 Sender ok\r\n");
621 * Implements the "RCPT To:" command
623 void smtp_rcpt(char *argbuf) {
625 char message_to_spammer[SIZ];
626 struct recptypes *valid = NULL;
628 if (strlen(SMTP->from) == 0) {
629 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
633 if (strncasecmp(argbuf, "To:", 3)) {
634 cprintf("501 5.1.7 Syntax error\r\n");
638 if ( (SMTP->is_msa) && (!CC->logged_in) ) {
640 "You must log in to send mail on this port.\r\n");
641 strcpy(SMTP->from, "");
645 strcpy(recp, &argbuf[3]);
647 stripallbut(recp, '<', '>');
649 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
650 cprintf("452 4.5.3 Too many recipients\r\n");
655 if ( (!CC->logged_in) /* Don't RBL authenticated users */
656 && (!SMTP->is_lmtp) ) { /* Don't RBL LMTP clients */
657 if (config.c_rbl_at_greeting == 0) { /* Don't RBL again if we already did it */
658 if (rbl_check(message_to_spammer)) {
659 cprintf("550 %s\r\n", message_to_spammer);
660 /* no need to free(valid), it's not allocated yet */
666 valid = validate_recipients(recp);
667 if (valid->num_error != 0) {
668 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
673 if (valid->num_internet > 0) {
675 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
676 cprintf("551 5.7.1 <%s> - you do not have permission to send Internet mail\r\n", recp);
683 if (valid->num_internet > 0) {
684 if ( (SMTP->message_originated_locally == 0)
685 && (SMTP->is_lmtp == 0) ) {
686 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
692 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
693 if (strlen(SMTP->recipients) > 0) {
694 strcat(SMTP->recipients, ",");
696 strcat(SMTP->recipients, recp);
697 SMTP->number_of_recipients += 1;
706 * Implements the DATA command
708 void smtp_data(void) {
710 struct CtdlMessage *msg = NULL;
713 struct recptypes *valid;
718 if (strlen(SMTP->from) == 0) {
719 cprintf("503 5.5.1 Need MAIL command first.\r\n");
723 if (SMTP->number_of_recipients < 1) {
724 cprintf("503 5.5.1 Need RCPT command first.\r\n");
728 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
730 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
733 if (body != NULL) snprintf(body, 4096,
734 "Received: from %s (%s [%s])\n"
742 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
745 "Unable to save message: internal error.\r\n");
749 lprintf(CTDL_DEBUG, "Converting message...\n");
750 msg = convert_internet_message(body);
752 /* If the user is locally authenticated, FORCE the From: header to
753 * show up as the real sender. Yes, this violates the RFC standard,
754 * but IT MAKES SENSE. If you prefer strict RFC adherence over
755 * common sense, you can disable this in the configuration.
757 * We also set the "message room name" ('O' field) to MAILROOM
758 * (which is Mail> on most systems) to prevent it from getting set
759 * to something ugly like "0000058008.Sent Items>" when the message
760 * is read with a Citadel client.
762 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
763 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
764 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
765 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
766 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
767 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
768 msg->cm_fields['A'] = strdup(CC->user.fullname);
769 msg->cm_fields['N'] = strdup(config.c_nodename);
770 msg->cm_fields['H'] = strdup(config.c_humannode);
771 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
772 msg->cm_fields['O'] = strdup(MAILROOM);
775 /* Set the "envelope from" address */
776 if (msg->cm_fields['P'] != NULL) {
777 free(msg->cm_fields['P']);
779 msg->cm_fields['P'] = strdup(SMTP->from);
781 /* Set the "envelope to" address */
782 if (msg->cm_fields['V'] != NULL) {
783 free(msg->cm_fields['V']);
785 msg->cm_fields['V'] = strdup(SMTP->recipients);
787 /* Submit the message into the Citadel system. */
788 valid = validate_recipients(SMTP->recipients);
790 /* If there are modules that want to scan this message before final
791 * submission (such as virus checkers or spam filters), call them now
792 * and give them an opportunity to reject the message.
794 if (SMTP->is_unfiltered) {
798 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
801 if (scan_errors > 0) { /* We don't want this message! */
803 if (msg->cm_fields['0'] == NULL) {
804 msg->cm_fields['0'] = strdup(
805 "5.7.1 Message rejected by filter");
808 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
811 else { /* Ok, we'll accept this message. */
812 msgnum = CtdlSubmitMsg(msg, valid, "");
814 sprintf(result, "250 2.0.0 Message accepted.\r\n");
817 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
821 /* For SMTP and ESTMP, just print the result message. For LMTP, we
822 * have to print one result message for each recipient. Since there
823 * is nothing in Citadel which would cause different recipients to
824 * have different results, we can get away with just spitting out the
825 * same message once for each recipient.
828 for (i=0; i<SMTP->number_of_recipients; ++i) {
829 cprintf("%s", result);
833 cprintf("%s", result);
836 /* Write something to the syslog (which may or may not be where the
837 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
840 syslog((LOG_MAIL | LOG_INFO),
841 "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
844 SMTP->number_of_recipients,
852 CtdlFreeMessage(msg);
854 smtp_data_clear(); /* clear out the buffers now */
859 * implements the STARTTLS command (Citadel API version)
862 void smtp_starttls(void)
864 char ok_response[SIZ];
865 char nosup_response[SIZ];
866 char error_response[SIZ];
869 "200 2.0.0 Begin TLS negotiation now\r\n");
870 sprintf(nosup_response,
871 "554 5.7.3 TLS not supported here\r\n");
872 sprintf(error_response,
873 "554 5.7.3 Internal error\r\n");
874 CtdlStartTLS(ok_response, nosup_response, error_response);
882 * Main command loop for SMTP sessions.
884 void smtp_command_loop(void) {
888 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
889 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
890 lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
894 lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
895 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
897 if (SMTP->command_state == smtp_user) {
898 smtp_get_user(cmdbuf);
901 else if (SMTP->command_state == smtp_password) {
902 smtp_get_pass(cmdbuf);
905 else if (SMTP->command_state == smtp_plain) {
906 smtp_try_plain(cmdbuf);
909 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
910 smtp_auth(&cmdbuf[5]);
913 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
917 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
918 smtp_expn(&cmdbuf[5]);
921 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
922 smtp_hello(&cmdbuf[5], 0);
925 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
926 smtp_hello(&cmdbuf[5], 1);
929 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
930 smtp_hello(&cmdbuf[5], 2);
933 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
937 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
938 smtp_mail(&cmdbuf[5]);
941 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
942 cprintf("250 NOOP\r\n");
945 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
946 cprintf("221 Goodbye...\r\n");
951 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
952 smtp_rcpt(&cmdbuf[5]);
955 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
959 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
963 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
964 smtp_vrfy(&cmdbuf[5]);
968 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
977 /*****************************************************************************/
978 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
979 /*****************************************************************************/
986 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
989 void smtp_try(const char *key, const char *addr, int *status,
990 char *dsn, size_t n, long msgnum)
997 char user[1024], node[1024], name[1024];
1010 /* Parse out the host portion of the recipient address */
1011 process_rfc822_addr(addr, user, node, name);
1013 lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
1016 /* Load the message out of the database */
1017 CC->redirect_buffer = malloc(SIZ);
1018 CC->redirect_len = 0;
1019 CC->redirect_alloc = SIZ;
1020 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
1021 msgtext = CC->redirect_buffer;
1022 msg_size = CC->redirect_len;
1023 CC->redirect_buffer = NULL;
1024 CC->redirect_len = 0;
1025 CC->redirect_alloc = 0;
1027 /* Extract something to send later in the 'MAIL From:' command */
1028 strcpy(mailfrom, "");
1032 if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
1035 if (!strncasecmp(buf, "From:", 5)) {
1036 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
1038 for (i=0; i<strlen(mailfrom); ++i) {
1039 if (!isprint(mailfrom[i])) {
1040 strcpy(&mailfrom[i], &mailfrom[i+1]);
1045 /* Strip out parenthesized names */
1048 for (i=0; i<strlen(mailfrom); ++i) {
1049 if (mailfrom[i] == '(') lp = i;
1050 if (mailfrom[i] == ')') rp = i;
1052 if ((lp>0)&&(rp>lp)) {
1053 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
1056 /* Prefer brokketized names */
1059 for (i=0; i<strlen(mailfrom); ++i) {
1060 if (mailfrom[i] == '<') lp = i;
1061 if (mailfrom[i] == '>') rp = i;
1063 if ( (lp>=0) && (rp>lp) ) {
1065 strcpy(mailfrom, &mailfrom[lp]);
1070 } while (scan_done == 0);
1071 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
1072 stripallbut(mailfrom, '<', '>');
1074 /* Figure out what mail exchanger host we have to connect to */
1075 num_mxhosts = getmx(mxhosts, node);
1076 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
1077 if (num_mxhosts < 1) {
1079 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
1084 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
1085 extract_token(buf, mxhosts, mx, '|', sizeof buf);
1086 strcpy(mx_user, "");
1087 strcpy(mx_pass, "");
1088 if (num_tokens(buf, '@') > 1) {
1089 extract_token(mx_user, buf, 0, '@', sizeof mx_user);
1090 if (num_tokens(mx_user, ':') > 1) {
1091 extract_token(mx_pass, mx_user, 1, ':', sizeof mx_pass);
1092 remove_token(mx_user, 1, ':');
1094 remove_token(buf, 0, '@');
1096 extract_token(mx_host, buf, 0, ':', sizeof mx_host);
1097 extract_token(mx_port, buf, 1, ':', sizeof mx_port);
1099 strcpy(mx_port, "25");
1101 lprintf(CTDL_DEBUG, "Trying %s : %s ...\n", mx_host, mx_port);
1102 sock = sock_connect(mx_host, mx_port, "tcp");
1103 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
1104 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
1107 snprintf(dsn, SIZ, "%s", strerror(errno));
1110 snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
1116 *status = 4; /* dsn is already filled in */
1120 /* Process the SMTP greeting from the server */
1121 if (ml_sock_gets(sock, buf) < 0) {
1123 strcpy(dsn, "Connection broken during SMTP conversation");
1126 lprintf(CTDL_DEBUG, "<%s\n", buf);
1127 if (buf[0] != '2') {
1128 if (buf[0] == '4') {
1130 safestrncpy(dsn, &buf[4], 1023);
1135 safestrncpy(dsn, &buf[4], 1023);
1140 /* At this point we know we are talking to a real SMTP server */
1142 /* Do a EHLO command. If it fails, try the HELO command. */
1143 snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
1144 lprintf(CTDL_DEBUG, ">%s", buf);
1145 sock_write(sock, buf, strlen(buf));
1146 if (ml_sock_gets(sock, buf) < 0) {
1148 strcpy(dsn, "Connection broken during SMTP HELO");
1151 lprintf(CTDL_DEBUG, "<%s\n", buf);
1152 if (buf[0] != '2') {
1153 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1154 lprintf(CTDL_DEBUG, ">%s", buf);
1155 sock_write(sock, buf, strlen(buf));
1156 if (ml_sock_gets(sock, buf) < 0) {
1158 strcpy(dsn, "Connection broken during SMTP HELO");
1162 if (buf[0] != '2') {
1163 if (buf[0] == '4') {
1165 safestrncpy(dsn, &buf[4], 1023);
1170 safestrncpy(dsn, &buf[4], 1023);
1175 /* Do an AUTH command if necessary */
1176 if (strlen(mx_user) > 0) {
1177 sprintf(buf, "%s%c%s%c%s%c", mx_user, 0, mx_user, 0, mx_pass, 0);
1178 CtdlEncodeBase64(mailfrom, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 3);
1179 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", mailfrom);
1180 lprintf(CTDL_DEBUG, ">%s", buf);
1181 sock_write(sock, buf, strlen(buf));
1182 if (ml_sock_gets(sock, buf) < 0) {
1184 strcpy(dsn, "Connection broken during SMTP AUTH");
1187 lprintf(CTDL_DEBUG, "<%s\n", buf);
1188 if (buf[0] != '2') {
1189 if (buf[0] == '4') {
1191 safestrncpy(dsn, &buf[4], 1023);
1196 safestrncpy(dsn, &buf[4], 1023);
1202 /* previous command succeeded, now try the MAIL From: command */
1203 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1204 lprintf(CTDL_DEBUG, ">%s", buf);
1205 sock_write(sock, buf, strlen(buf));
1206 if (ml_sock_gets(sock, buf) < 0) {
1208 strcpy(dsn, "Connection broken during SMTP MAIL");
1211 lprintf(CTDL_DEBUG, "<%s\n", buf);
1212 if (buf[0] != '2') {
1213 if (buf[0] == '4') {
1215 safestrncpy(dsn, &buf[4], 1023);
1220 safestrncpy(dsn, &buf[4], 1023);
1225 /* MAIL succeeded, now try the RCPT To: command */
1226 snprintf(buf, sizeof buf, "RCPT To: <%s@%s>\r\n", user, node);
1227 lprintf(CTDL_DEBUG, ">%s", buf);
1228 sock_write(sock, buf, strlen(buf));
1229 if (ml_sock_gets(sock, buf) < 0) {
1231 strcpy(dsn, "Connection broken during SMTP RCPT");
1234 lprintf(CTDL_DEBUG, "<%s\n", buf);
1235 if (buf[0] != '2') {
1236 if (buf[0] == '4') {
1238 safestrncpy(dsn, &buf[4], 1023);
1243 safestrncpy(dsn, &buf[4], 1023);
1248 /* RCPT succeeded, now try the DATA command */
1249 lprintf(CTDL_DEBUG, ">DATA\n");
1250 sock_write(sock, "DATA\r\n", 6);
1251 if (ml_sock_gets(sock, buf) < 0) {
1253 strcpy(dsn, "Connection broken during SMTP DATA");
1256 lprintf(CTDL_DEBUG, "<%s\n", buf);
1257 if (buf[0] != '3') {
1258 if (buf[0] == '4') {
1260 safestrncpy(dsn, &buf[4], 1023);
1265 safestrncpy(dsn, &buf[4], 1023);
1270 /* If we reach this point, the server is expecting data */
1271 sock_write(sock, msgtext, msg_size);
1272 if (msgtext[msg_size-1] != 10) {
1273 lprintf(CTDL_WARNING, "Possible problem: message did not "
1274 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1278 sock_write(sock, ".\r\n", 3);
1279 if (ml_sock_gets(sock, buf) < 0) {
1281 strcpy(dsn, "Connection broken during SMTP message transmit");
1284 lprintf(CTDL_DEBUG, "%s\n", buf);
1285 if (buf[0] != '2') {
1286 if (buf[0] == '4') {
1288 safestrncpy(dsn, &buf[4], 1023);
1293 safestrncpy(dsn, &buf[4], 1023);
1299 safestrncpy(dsn, &buf[4], 1023);
1302 lprintf(CTDL_DEBUG, ">QUIT\n");
1303 sock_write(sock, "QUIT\r\n", 6);
1304 ml_sock_gets(sock, buf);
1305 lprintf(CTDL_DEBUG, "<%s\n", buf);
1306 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1309 bail: free(msgtext);
1312 /* Write something to the syslog (which may or may not be where the
1313 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1315 if (enable_syslog) {
1316 syslog((LOG_MAIL | LOG_INFO),
1317 "%ld: to=<%s>, relay=%s, stat=%s",
1331 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1332 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1333 * a "bounce" message (delivery status notification).
1335 void smtp_do_bounce(char *instr) {
1343 char bounceto[1024];
1344 int num_bounces = 0;
1345 int bounce_this = 0;
1346 long bounce_msgid = (-1);
1347 time_t submitted = 0L;
1348 struct CtdlMessage *bmsg = NULL;
1350 struct recptypes *valid;
1351 int successful_bounce = 0;
1353 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1354 strcpy(bounceto, "");
1356 lines = num_tokens(instr, '\n');
1359 /* See if it's time to give up on delivery of this message */
1360 for (i=0; i<lines; ++i) {
1361 extract_token(buf, instr, i, '\n', sizeof buf);
1362 extract_token(key, buf, 0, '|', sizeof key);
1363 extract_token(addr, buf, 1, '|', sizeof addr);
1364 if (!strcasecmp(key, "submitted")) {
1365 submitted = atol(addr);
1369 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1373 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1374 if (bmsg == NULL) return;
1375 memset(bmsg, 0, sizeof(struct CtdlMessage));
1377 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1378 bmsg->cm_anon_type = MES_NORMAL;
1379 bmsg->cm_format_type = 1;
1380 bmsg->cm_fields['A'] = strdup("Citadel");
1381 bmsg->cm_fields['O'] = strdup(MAILROOM);
1382 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1383 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1385 if (give_up) bmsg->cm_fields['M'] = strdup(
1386 "A message you sent could not be delivered to some or all of its recipients\n"
1387 "due to prolonged unavailability of its destination(s).\n"
1388 "Giving up on the following addresses:\n\n"
1391 else bmsg->cm_fields['M'] = strdup(
1392 "A message you sent could not be delivered to some or all of its recipients.\n"
1393 "The following addresses were undeliverable:\n\n"
1397 * Now go through the instructions checking for stuff.
1399 for (i=0; i<lines; ++i) {
1400 extract_token(buf, instr, i, '\n', sizeof buf);
1401 extract_token(key, buf, 0, '|', sizeof key);
1402 extract_token(addr, buf, 1, '|', sizeof addr);
1403 status = extract_int(buf, 2);
1404 extract_token(dsn, buf, 3, '|', sizeof dsn);
1407 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1408 key, addr, status, dsn);
1410 if (!strcasecmp(key, "bounceto")) {
1411 strcpy(bounceto, addr);
1415 (!strcasecmp(key, "local"))
1416 || (!strcasecmp(key, "remote"))
1417 || (!strcasecmp(key, "ignet"))
1418 || (!strcasecmp(key, "room"))
1420 if (status == 5) bounce_this = 1;
1421 if (give_up) bounce_this = 1;
1427 if (bmsg->cm_fields['M'] == NULL) {
1428 lprintf(CTDL_ERR, "ERROR ... M field is null "
1429 "(%s:%d)\n", __FILE__, __LINE__);
1432 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1433 strlen(bmsg->cm_fields['M']) + 1024 );
1434 strcat(bmsg->cm_fields['M'], addr);
1435 strcat(bmsg->cm_fields['M'], ": ");
1436 strcat(bmsg->cm_fields['M'], dsn);
1437 strcat(bmsg->cm_fields['M'], "\n");
1439 remove_token(instr, i, '\n');
1445 /* Deliver the bounce if there's anything worth mentioning */
1446 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1447 if (num_bounces > 0) {
1449 /* First try the user who sent the message */
1450 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1451 if (strlen(bounceto) == 0) {
1452 lprintf(CTDL_ERR, "No bounce address specified\n");
1453 bounce_msgid = (-1L);
1456 /* Can we deliver the bounce to the original sender? */
1457 valid = validate_recipients(bounceto);
1458 if (valid != NULL) {
1459 if (valid->num_error == 0) {
1460 CtdlSubmitMsg(bmsg, valid, "");
1461 successful_bounce = 1;
1465 /* If not, post it in the Aide> room */
1466 if (successful_bounce == 0) {
1467 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1470 /* Free up the memory we used */
1471 if (valid != NULL) {
1476 CtdlFreeMessage(bmsg);
1477 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1482 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1483 * set of delivery instructions for completed deliveries and remove them.
1485 * It returns the number of incomplete deliveries remaining.
1487 int smtp_purge_completed_deliveries(char *instr) {
1498 lines = num_tokens(instr, '\n');
1499 for (i=0; i<lines; ++i) {
1500 extract_token(buf, instr, i, '\n', sizeof buf);
1501 extract_token(key, buf, 0, '|', sizeof key);
1502 extract_token(addr, buf, 1, '|', sizeof addr);
1503 status = extract_int(buf, 2);
1504 extract_token(dsn, buf, 3, '|', sizeof dsn);
1509 (!strcasecmp(key, "local"))
1510 || (!strcasecmp(key, "remote"))
1511 || (!strcasecmp(key, "ignet"))
1512 || (!strcasecmp(key, "room"))
1514 if (status == 2) completed = 1;
1519 remove_token(instr, i, '\n');
1532 * Called by smtp_do_queue() to handle an individual message.
1534 void smtp_do_procmsg(long msgnum, void *userdata) {
1535 struct CtdlMessage *msg = NULL;
1537 char *results = NULL;
1545 long text_msgid = (-1);
1546 int incomplete_deliveries_remaining;
1547 time_t attempted = 0L;
1548 time_t last_attempted = 0L;
1549 time_t retry = SMTP_RETRY_INTERVAL;
1551 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1553 msg = CtdlFetchMessage(msgnum, 1);
1555 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1559 instr = strdup(msg->cm_fields['M']);
1560 CtdlFreeMessage(msg);
1562 /* Strip out the headers amd any other non-instruction line */
1563 lines = num_tokens(instr, '\n');
1564 for (i=0; i<lines; ++i) {
1565 extract_token(buf, instr, i, '\n', sizeof buf);
1566 if (num_tokens(buf, '|') < 2) {
1567 remove_token(instr, i, '\n');
1573 /* Learn the message ID and find out about recent delivery attempts */
1574 lines = num_tokens(instr, '\n');
1575 for (i=0; i<lines; ++i) {
1576 extract_token(buf, instr, i, '\n', sizeof buf);
1577 extract_token(key, buf, 0, '|', sizeof key);
1578 if (!strcasecmp(key, "msgid")) {
1579 text_msgid = extract_long(buf, 1);
1581 if (!strcasecmp(key, "retry")) {
1582 /* double the retry interval after each attempt */
1583 retry = extract_long(buf, 1) * 2L;
1584 if (retry > SMTP_RETRY_MAX) {
1585 retry = SMTP_RETRY_MAX;
1587 remove_token(instr, i, '\n');
1589 if (!strcasecmp(key, "attempted")) {
1590 attempted = extract_long(buf, 1);
1591 if (attempted > last_attempted)
1592 last_attempted = attempted;
1597 * Postpone delivery if we've already tried recently.
1599 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1600 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1607 * Bail out if there's no actual message associated with this
1609 if (text_msgid < 0L) {
1610 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1615 /* Plow through the instructions looking for 'remote' directives and
1616 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1617 * were experienced and it's time to try again)
1619 lines = num_tokens(instr, '\n');
1620 for (i=0; i<lines; ++i) {
1621 extract_token(buf, instr, i, '\n', sizeof buf);
1622 extract_token(key, buf, 0, '|', sizeof key);
1623 extract_token(addr, buf, 1, '|', sizeof addr);
1624 status = extract_int(buf, 2);
1625 extract_token(dsn, buf, 3, '|', sizeof dsn);
1626 if ( (!strcasecmp(key, "remote"))
1627 && ((status==0)||(status==3)||(status==4)) ) {
1629 /* Remove this "remote" instruction from the set,
1630 * but replace the set's final newline if
1631 * remove_token() stripped it. It has to be there.
1633 remove_token(instr, i, '\n');
1634 if (instr[strlen(instr)-1] != '\n') {
1635 strcat(instr, "\n");
1640 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1641 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1643 if (results == NULL) {
1644 results = malloc(1024);
1645 memset(results, 0, 1024);
1648 results = realloc(results,
1649 strlen(results) + 1024);
1651 snprintf(&results[strlen(results)], 1024,
1653 key, addr, status, dsn);
1658 if (results != NULL) {
1659 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1660 strcat(instr, results);
1665 /* Generate 'bounce' messages */
1666 smtp_do_bounce(instr);
1668 /* Go through the delivery list, deleting completed deliveries */
1669 incomplete_deliveries_remaining =
1670 smtp_purge_completed_deliveries(instr);
1674 * No delivery instructions remain, so delete both the instructions
1675 * message and the message message.
1677 if (incomplete_deliveries_remaining <= 0) {
1679 delmsgs[0] = msgnum;
1680 delmsgs[1] = text_msgid;
1681 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
1685 * Uncompleted delivery instructions remain, so delete the old
1686 * instructions and replace with the updated ones.
1688 if (incomplete_deliveries_remaining > 0) {
1689 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
1690 msg = malloc(sizeof(struct CtdlMessage));
1691 memset(msg, 0, sizeof(struct CtdlMessage));
1692 msg->cm_magic = CTDLMESSAGE_MAGIC;
1693 msg->cm_anon_type = MES_NORMAL;
1694 msg->cm_format_type = FMT_RFC822;
1695 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1696 snprintf(msg->cm_fields['M'],
1698 "Content-type: %s\n\n%s\n"
1701 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1702 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1703 CtdlFreeMessage(msg);
1714 * Run through the queue sending out messages.
1716 void smtp_do_queue(void) {
1717 static int doing_queue = 0;
1720 * This is a simple concurrency check to make sure only one queue run
1721 * is done at a time. We could do this with a mutex, but since we
1722 * don't really require extremely fine granularity here, we'll do it
1723 * with a static variable instead.
1725 if (doing_queue) return;
1729 * Go ahead and run the queue
1731 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1733 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1734 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1737 CtdlForEachMessage(MSGS_ALL, 0L, NULL,
1738 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1740 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1747 /*****************************************************************************/
1748 /* SMTP UTILITY COMMANDS */
1749 /*****************************************************************************/
1751 void cmd_smtp(char *argbuf) {
1758 if (CtdlAccessCheck(ac_aide)) return;
1760 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1762 if (!strcasecmp(cmd, "mx")) {
1763 extract_token(node, argbuf, 1, '|', sizeof node);
1764 num_mxhosts = getmx(buf, node);
1765 cprintf("%d %d MX hosts listed for %s\n",
1766 LISTING_FOLLOWS, num_mxhosts, node);
1767 for (i=0; i<num_mxhosts; ++i) {
1768 extract_token(node, buf, i, '|', sizeof node);
1769 cprintf("%s\n", node);
1775 else if (!strcasecmp(cmd, "runqueue")) {
1777 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1782 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1789 * Initialize the SMTP outbound queue
1791 void smtp_init_spoolout(void) {
1792 struct ctdlroom qrbuf;
1795 * Create the room. This will silently fail if the room already
1796 * exists, and that's perfectly ok, because we want it to exist.
1798 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1801 * Make sure it's set to be a "system room" so it doesn't show up
1802 * in the <K>nown rooms list for Aides.
1804 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1805 qrbuf.QRflags2 |= QR2_SYSTEM;
1813 /*****************************************************************************/
1814 /* MODULE INITIALIZATION STUFF */
1815 /*****************************************************************************/
1817 * This cleanup function blows away the temporary memory used by
1820 void smtp_cleanup_function(void) {
1822 /* Don't do this stuff if this is not an SMTP session! */
1823 if (CC->h_command_function != smtp_command_loop) return;
1825 lprintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1835 char *serv_smtp_init(void)
1838 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1845 CtdlRegisterServiceHook(config.c_smtps_port,
1852 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1858 CtdlRegisterServiceHook(0, /* local LMTP */
1864 CtdlRegisterServiceHook(0, /* local LMTP */
1865 file_lmtp_unfiltered_socket,
1866 lmtp_unfiltered_greeting,
1870 smtp_init_spoolout();
1871 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1872 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1873 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");