4 * This module is an SMTP and ESMTP implementation for the Citadel system.
5 * It is compliant with all of the following:
7 * RFC 821 - Simple Mail Transfer Protocol
8 * RFC 876 - Survey of SMTP Implementations
9 * RFC 1047 - Duplicate messages and SMTP
10 * RFC 1854 - command pipelining
11 * RFC 1869 - Extended Simple Mail Transfer Protocol
12 * RFC 1870 - SMTP Service Extension for Message Size Declaration
13 * RFC 1893 - Enhanced Mail System Status Codes
14 * RFC 2033 - Local Mail Transfer Protocol
15 * RFC 2034 - SMTP Service Extension for Returning Enhanced Error Codes
16 * RFC 2197 - SMTP Service Extension for Command Pipelining
17 * RFC 2476 - Message Submission
18 * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
19 * RFC 2554 - SMTP Service Extension for Authentication
20 * RFC 2821 - Simple Mail Transfer Protocol
21 * RFC 2822 - Internet Message Format
22 * RFC 2920 - SMTP Service Extension for Command Pipelining
24 * The VRFY and EXPN commands have been removed from this implementation
25 * because nobody uses these commands anymore, except for spammers.
37 #include <sys/types.h>
40 #if TIME_WITH_SYS_TIME
41 # include <sys/time.h>
45 # include <sys/time.h>
55 #include <sys/socket.h>
56 #include <netinet/in.h>
57 #include <arpa/inet.h>
60 #include "citserver.h"
70 #include "internet_addressing.h"
73 #include "clientsocket.h"
74 #include "locate_host.h"
75 #include "citadel_dirs.h"
84 #include "ctdl_module.h"
88 struct citsmtp { /* Information about the current session */
93 int number_of_recipients;
95 int message_originated_locally;
101 enum { /* Command states for login authentication */
108 #define SMTP CC->SMTP
111 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
115 /*****************************************************************************/
116 /* SMTP SERVER (INBOUND) STUFF */
117 /*****************************************************************************/
121 * Here's where our SMTP session begins its happy day.
123 void smtp_greeting(int is_msa)
125 char message_to_spammer[1024];
127 strcpy(CC->cs_clientname, "SMTP session");
128 CC->internal_pgm = 1;
129 CC->cs_flags |= CS_STEALTH;
130 SMTP = malloc(sizeof(struct citsmtp));
131 memset(SMTP, 0, sizeof(struct citsmtp));
132 SMTP->is_msa = is_msa;
134 /* If this config option is set, reject connections from problem
135 * addresses immediately instead of after they execute a RCPT
137 if ( (config.c_rbl_at_greeting) && (SMTP->is_msa == 0) ) {
138 if (rbl_check(message_to_spammer)) {
139 cprintf("550 %s\r\n", message_to_spammer);
141 /* no need to free_recipients(valid), it's not allocated yet */
146 /* Otherwise we're either clean or we check later. */
148 if (CC->nologin==1) {
149 cprintf("500 Too many users are already online (maximum is %d)\r\n",
153 /* no need to free_recipients(valid), it's not allocated yet */
157 /* Note: the FQDN *must* appear as the first thing after the 220 code.
158 * Some clients (including citmail.c) depend on it being there.
160 cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn);
165 * SMTPS is just like SMTP, except it goes crypto right away.
167 void smtps_greeting(void) {
168 CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
170 if (!CC->redirect_ssl) CC->kill_me = 1; /* kill session if no crypto */
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 (!IsEmptyStr(ident)) {
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 (!IsEmptyStr(SMTP->from)) {
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 (IsEmptyStr(SMTP->from)) {
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 (IsEmptyStr(SMTP->from)) {
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 (!IsEmptyStr(SMTP->recipients)) {
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 (IsEmptyStr(SMTP->from)) {
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, 0);
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)
797 void smtp_starttls(void)
799 char ok_response[SIZ];
800 char nosup_response[SIZ];
801 char error_response[SIZ];
804 "220 2.0.0 Begin TLS negotiation now\r\n");
805 sprintf(nosup_response,
806 "554 5.7.3 TLS not supported here\r\n");
807 sprintf(error_response,
808 "554 5.7.3 Internal error\r\n");
809 CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
816 * Main command loop for SMTP sessions.
818 void smtp_command_loop(void) {
822 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
823 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
824 lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
828 lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
829 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
831 if (SMTP->command_state == smtp_user) {
832 smtp_get_user(cmdbuf);
835 else if (SMTP->command_state == smtp_password) {
836 smtp_get_pass(cmdbuf);
839 else if (SMTP->command_state == smtp_plain) {
840 smtp_try_plain(cmdbuf);
843 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
844 smtp_auth(&cmdbuf[5]);
847 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
851 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
852 smtp_hello(&cmdbuf[5], 0);
855 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
856 smtp_hello(&cmdbuf[5], 1);
859 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
860 smtp_hello(&cmdbuf[5], 2);
863 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
867 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
868 smtp_mail(&cmdbuf[5]);
871 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
872 cprintf("250 NOOP\r\n");
875 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
876 cprintf("221 Goodbye...\r\n");
881 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
882 smtp_rcpt(&cmdbuf[5]);
885 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
889 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
894 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
903 /*****************************************************************************/
904 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
905 /*****************************************************************************/
912 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
915 void smtp_try(const char *key, const char *addr, int *status,
916 char *dsn, size_t n, long msgnum)
923 char user[1024], node[1024], name[1024];
936 /* Parse out the host portion of the recipient address */
937 process_rfc822_addr(addr, user, node, name);
939 lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
942 /* Load the message out of the database */
943 CC->redirect_buffer = malloc(SIZ);
944 CC->redirect_len = 0;
945 CC->redirect_alloc = SIZ;
946 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
947 msgtext = CC->redirect_buffer;
948 msg_size = CC->redirect_len;
949 CC->redirect_buffer = NULL;
950 CC->redirect_len = 0;
951 CC->redirect_alloc = 0;
953 /* Extract something to send later in the 'MAIL From:' command */
954 strcpy(mailfrom, "");
958 if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
961 if (!strncasecmp(buf, "From:", 5)) {
962 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
964 for (i=0; mailfrom[i]; ++i) {
965 if (!isprint(mailfrom[i])) {
966 strcpy(&mailfrom[i], &mailfrom[i+1]);
971 /* Strip out parenthesized names */
974 for (i=0; mailfrom[i]; ++i) {
975 if (mailfrom[i] == '(') lp = i;
976 if (mailfrom[i] == ')') rp = i;
978 if ((lp>0)&&(rp>lp)) {
979 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
982 /* Prefer brokketized names */
985 for (i=0; mailfrom[i]; ++i) {
986 if (mailfrom[i] == '<') lp = i;
987 if (mailfrom[i] == '>') rp = i;
989 if ( (lp>=0) && (rp>lp) ) {
991 strcpy(mailfrom, &mailfrom[lp]);
996 } while (scan_done == 0);
997 if (IsEmptyStr(mailfrom)) strcpy(mailfrom, "someone@somewhere.org");
998 stripallbut(mailfrom, '<', '>');
1000 /* Figure out what mail exchanger host we have to connect to */
1001 num_mxhosts = getmx(mxhosts, node);
1002 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
1003 if (num_mxhosts < 1) {
1005 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
1010 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
1012 extract_token(buf, mxhosts, mx, '|', sizeof buf);
1013 strcpy(mx_user, "");
1014 strcpy(mx_pass, "");
1015 if (num_tokens(buf, '@') > 1) {
1016 strcpy (mx_user, buf);
1017 endpart = strrchr(mx_user, '@');
1019 strcpy (mx_host, endpart + 1);
1020 endpart = strrchr(mx_user, ':');
1021 if (endpart != NULL) {
1022 strcpy(mx_pass, endpart+1);
1027 strcpy (mx_host, buf);
1028 endpart = strrchr(mx_host, ':');
1031 strcpy(mx_port, endpart + 1);
1034 strcpy(mx_port, "25");
1036 lprintf(CTDL_DEBUG, "Trying %s : %s ...\n", mx_host, mx_port);
1037 sock = sock_connect(mx_host, mx_port, "tcp");
1038 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
1039 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
1042 snprintf(dsn, SIZ, "%s", strerror(errno));
1045 snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
1051 *status = 4; /* dsn is already filled in */
1055 /* Process the SMTP greeting from the server */
1056 if (ml_sock_gets(sock, buf) < 0) {
1058 strcpy(dsn, "Connection broken during SMTP conversation");
1061 lprintf(CTDL_DEBUG, "<%s\n", buf);
1062 if (buf[0] != '2') {
1063 if (buf[0] == '4') {
1065 safestrncpy(dsn, &buf[4], 1023);
1070 safestrncpy(dsn, &buf[4], 1023);
1075 /* At this point we know we are talking to a real SMTP server */
1077 /* Do a EHLO command. If it fails, try the HELO command. */
1078 snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
1079 lprintf(CTDL_DEBUG, ">%s", buf);
1080 sock_write(sock, buf, strlen(buf));
1081 if (ml_sock_gets(sock, buf) < 0) {
1083 strcpy(dsn, "Connection broken during SMTP HELO");
1086 lprintf(CTDL_DEBUG, "<%s\n", buf);
1087 if (buf[0] != '2') {
1088 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1089 lprintf(CTDL_DEBUG, ">%s", buf);
1090 sock_write(sock, buf, strlen(buf));
1091 if (ml_sock_gets(sock, buf) < 0) {
1093 strcpy(dsn, "Connection broken during SMTP HELO");
1097 if (buf[0] != '2') {
1098 if (buf[0] == '4') {
1100 safestrncpy(dsn, &buf[4], 1023);
1105 safestrncpy(dsn, &buf[4], 1023);
1110 /* Do an AUTH command if necessary */
1111 if (!IsEmptyStr(mx_user)) {
1113 sprintf(buf, "%s%c%s%c%s", mx_user, '\0', mx_user, '\0', mx_pass);
1114 CtdlEncodeBase64(encoded, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 2);
1115 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", encoded);
1116 lprintf(CTDL_DEBUG, ">%s", buf);
1117 sock_write(sock, buf, strlen(buf));
1118 if (ml_sock_gets(sock, buf) < 0) {
1120 strcpy(dsn, "Connection broken during SMTP AUTH");
1123 lprintf(CTDL_DEBUG, "<%s\n", buf);
1124 if (buf[0] != '2') {
1125 if (buf[0] == '4') {
1127 safestrncpy(dsn, &buf[4], 1023);
1132 safestrncpy(dsn, &buf[4], 1023);
1138 /* previous command succeeded, now try the MAIL From: command */
1139 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1140 lprintf(CTDL_DEBUG, ">%s", buf);
1141 sock_write(sock, buf, strlen(buf));
1142 if (ml_sock_gets(sock, buf) < 0) {
1144 strcpy(dsn, "Connection broken during SMTP MAIL");
1147 lprintf(CTDL_DEBUG, "<%s\n", buf);
1148 if (buf[0] != '2') {
1149 if (buf[0] == '4') {
1151 safestrncpy(dsn, &buf[4], 1023);
1156 safestrncpy(dsn, &buf[4], 1023);
1161 /* MAIL succeeded, now try the RCPT To: command */
1162 snprintf(buf, sizeof buf, "RCPT To: <%s@%s>\r\n", user, node);
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 RCPT");
1170 lprintf(CTDL_DEBUG, "<%s\n", buf);
1171 if (buf[0] != '2') {
1172 if (buf[0] == '4') {
1174 safestrncpy(dsn, &buf[4], 1023);
1179 safestrncpy(dsn, &buf[4], 1023);
1184 /* RCPT succeeded, now try the DATA command */
1185 lprintf(CTDL_DEBUG, ">DATA\n");
1186 sock_write(sock, "DATA\r\n", 6);
1187 if (ml_sock_gets(sock, buf) < 0) {
1189 strcpy(dsn, "Connection broken during SMTP DATA");
1192 lprintf(CTDL_DEBUG, "<%s\n", buf);
1193 if (buf[0] != '3') {
1194 if (buf[0] == '4') {
1196 safestrncpy(dsn, &buf[4], 1023);
1201 safestrncpy(dsn, &buf[4], 1023);
1206 /* If we reach this point, the server is expecting data */
1207 sock_write(sock, msgtext, msg_size);
1208 if (msgtext[msg_size-1] != 10) {
1209 lprintf(CTDL_WARNING, "Possible problem: message did not "
1210 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1214 sock_write(sock, ".\r\n", 3);
1215 if (ml_sock_gets(sock, buf) < 0) {
1217 strcpy(dsn, "Connection broken during SMTP message transmit");
1220 lprintf(CTDL_DEBUG, "%s\n", buf);
1221 if (buf[0] != '2') {
1222 if (buf[0] == '4') {
1224 safestrncpy(dsn, &buf[4], 1023);
1229 safestrncpy(dsn, &buf[4], 1023);
1235 safestrncpy(dsn, &buf[4], 1023);
1238 lprintf(CTDL_DEBUG, ">QUIT\n");
1239 sock_write(sock, "QUIT\r\n", 6);
1240 ml_sock_gets(sock, buf);
1241 lprintf(CTDL_DEBUG, "<%s\n", buf);
1242 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1245 bail: free(msgtext);
1248 /* Write something to the syslog (which may or may not be where the
1249 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1251 if (enable_syslog) {
1252 syslog((LOG_MAIL | LOG_INFO),
1253 "%ld: to=<%s>, relay=%s, stat=%s",
1267 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1268 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1269 * a "bounce" message (delivery status notification).
1271 void smtp_do_bounce(char *instr) {
1279 char bounceto[1024];
1281 int num_bounces = 0;
1282 int bounce_this = 0;
1283 long bounce_msgid = (-1);
1284 time_t submitted = 0L;
1285 struct CtdlMessage *bmsg = NULL;
1287 struct recptypes *valid;
1288 int successful_bounce = 0;
1294 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1295 strcpy(bounceto, "");
1296 sprintf(boundary, "=_Citadel_Multipart_%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
1297 lines = num_tokens(instr, '\n');
1299 /* See if it's time to give up on delivery of this message */
1300 for (i=0; i<lines; ++i) {
1301 extract_token(buf, instr, i, '\n', sizeof buf);
1302 extract_token(key, buf, 0, '|', sizeof key);
1303 extract_token(addr, buf, 1, '|', sizeof addr);
1304 if (!strcasecmp(key, "submitted")) {
1305 submitted = atol(addr);
1309 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1313 /* Start building our bounce message */
1315 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1316 if (bmsg == NULL) return;
1317 memset(bmsg, 0, sizeof(struct CtdlMessage));
1319 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1320 bmsg->cm_anon_type = MES_NORMAL;
1321 bmsg->cm_format_type = FMT_RFC822;
1322 bmsg->cm_fields['A'] = strdup("Citadel");
1323 bmsg->cm_fields['O'] = strdup(MAILROOM);
1324 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1325 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1326 bmsg->cm_fields['M'] = malloc(1024);
1328 strcpy(bmsg->cm_fields['M'], "Content-type: multipart/mixed; boundary=\"");
1329 strcat(bmsg->cm_fields['M'], boundary);
1330 strcat(bmsg->cm_fields['M'], "\"\r\n");
1331 strcat(bmsg->cm_fields['M'], "MIME-Version: 1.0\r\n");
1332 strcat(bmsg->cm_fields['M'], "X-Mailer: " CITADEL "\r\n");
1333 strcat(bmsg->cm_fields['M'], "\r\nThis is a multipart message in MIME format.\r\n\r\n");
1334 strcat(bmsg->cm_fields['M'], "--");
1335 strcat(bmsg->cm_fields['M'], boundary);
1336 strcat(bmsg->cm_fields['M'], "\r\n");
1337 strcat(bmsg->cm_fields['M'], "Content-type: text/plain\r\n\r\n");
1339 if (give_up) strcat(bmsg->cm_fields['M'],
1340 "A message you sent could not be delivered to some or all of its recipients\n"
1341 "due to prolonged unavailability of its destination(s).\n"
1342 "Giving up on the following addresses:\n\n"
1345 else strcat(bmsg->cm_fields['M'],
1346 "A message you sent could not be delivered to some or all of its recipients.\n"
1347 "The following addresses were undeliverable:\n\n"
1351 * Now go through the instructions checking for stuff.
1353 for (i=0; i<lines; ++i) {
1354 extract_token(buf, instr, i, '\n', sizeof buf);
1355 extract_token(key, buf, 0, '|', sizeof key);
1356 extract_token(addr, buf, 1, '|', sizeof addr);
1357 status = extract_int(buf, 2);
1358 extract_token(dsn, buf, 3, '|', sizeof dsn);
1361 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1362 key, addr, status, dsn);
1364 if (!strcasecmp(key, "bounceto")) {
1365 strcpy(bounceto, addr);
1368 if (!strcasecmp(key, "msgid")) {
1369 omsgid = atol(addr);
1372 if (!strcasecmp(key, "remote")) {
1373 if (status == 5) bounce_this = 1;
1374 if (give_up) bounce_this = 1;
1380 if (bmsg->cm_fields['M'] == NULL) {
1381 lprintf(CTDL_ERR, "ERROR ... M field is null "
1382 "(%s:%d)\n", __FILE__, __LINE__);
1385 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1386 strlen(bmsg->cm_fields['M']) + 1024 );
1387 strcat(bmsg->cm_fields['M'], addr);
1388 strcat(bmsg->cm_fields['M'], ": ");
1389 strcat(bmsg->cm_fields['M'], dsn);
1390 strcat(bmsg->cm_fields['M'], "\r\n");
1392 remove_token(instr, i, '\n');
1398 /* Attach the original message */
1400 strcat(bmsg->cm_fields['M'], "--");
1401 strcat(bmsg->cm_fields['M'], boundary);
1402 strcat(bmsg->cm_fields['M'], "\r\n");
1403 strcat(bmsg->cm_fields['M'], "Content-type: message/rfc822\r\n");
1404 strcat(bmsg->cm_fields['M'], "Content-Transfer-Encoding: 7bit\r\n");
1405 strcat(bmsg->cm_fields['M'], "Content-Disposition: inline\r\n");
1406 strcat(bmsg->cm_fields['M'], "\r\n");
1408 CC->redirect_buffer = malloc(SIZ);
1409 CC->redirect_len = 0;
1410 CC->redirect_alloc = SIZ;
1411 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
1412 omsgtext = CC->redirect_buffer;
1413 omsgsize = CC->redirect_len;
1414 CC->redirect_buffer = NULL;
1415 CC->redirect_len = 0;
1416 CC->redirect_alloc = 0;
1417 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1418 (strlen(bmsg->cm_fields['M']) + omsgsize + 1024) );
1419 strcat(bmsg->cm_fields['M'], omsgtext);
1423 /* Close the multipart MIME scope */
1424 strcat(bmsg->cm_fields['M'], "--");
1425 strcat(bmsg->cm_fields['M'], boundary);
1426 strcat(bmsg->cm_fields['M'], "--\r\n");
1428 /* Deliver the bounce if there's anything worth mentioning */
1429 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1430 if (num_bounces > 0) {
1432 /* First try the user who sent the message */
1433 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1434 if (IsEmptyStr(bounceto)) {
1435 lprintf(CTDL_ERR, "No bounce address specified\n");
1436 bounce_msgid = (-1L);
1439 /* Can we deliver the bounce to the original sender? */
1440 valid = validate_recipients(bounceto);
1441 if (valid != NULL) {
1442 if (valid->num_error == 0) {
1443 CtdlSubmitMsg(bmsg, valid, "");
1444 successful_bounce = 1;
1448 /* If not, post it in the Aide> room */
1449 if (successful_bounce == 0) {
1450 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1453 /* Free up the memory we used */
1454 if (valid != NULL) {
1455 free_recipients(valid);
1459 CtdlFreeMessage(bmsg);
1460 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1465 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1466 * set of delivery instructions for completed deliveries and remove them.
1468 * It returns the number of incomplete deliveries remaining.
1470 int smtp_purge_completed_deliveries(char *instr) {
1481 lines = num_tokens(instr, '\n');
1482 for (i=0; i<lines; ++i) {
1483 extract_token(buf, instr, i, '\n', sizeof buf);
1484 extract_token(key, buf, 0, '|', sizeof key);
1485 extract_token(addr, buf, 1, '|', sizeof addr);
1486 status = extract_int(buf, 2);
1487 extract_token(dsn, buf, 3, '|', sizeof dsn);
1491 if (!strcasecmp(key, "remote")) {
1492 if (status == 2) completed = 1;
1497 remove_token(instr, i, '\n');
1510 * Called by smtp_do_queue() to handle an individual message.
1512 void smtp_do_procmsg(long msgnum, void *userdata) {
1513 struct CtdlMessage *msg = NULL;
1515 char *results = NULL;
1523 long text_msgid = (-1);
1524 int incomplete_deliveries_remaining;
1525 time_t attempted = 0L;
1526 time_t last_attempted = 0L;
1527 time_t retry = SMTP_RETRY_INTERVAL;
1529 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1531 msg = CtdlFetchMessage(msgnum, 1);
1533 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1537 instr = strdup(msg->cm_fields['M']);
1538 CtdlFreeMessage(msg);
1540 /* Strip out the headers amd any other non-instruction line */
1541 lines = num_tokens(instr, '\n');
1542 for (i=0; i<lines; ++i) {
1543 extract_token(buf, instr, i, '\n', sizeof buf);
1544 if (num_tokens(buf, '|') < 2) {
1545 remove_token(instr, i, '\n');
1551 /* Learn the message ID and find out about recent delivery attempts */
1552 lines = num_tokens(instr, '\n');
1553 for (i=0; i<lines; ++i) {
1554 extract_token(buf, instr, i, '\n', sizeof buf);
1555 extract_token(key, buf, 0, '|', sizeof key);
1556 if (!strcasecmp(key, "msgid")) {
1557 text_msgid = extract_long(buf, 1);
1559 if (!strcasecmp(key, "retry")) {
1560 /* double the retry interval after each attempt */
1561 retry = extract_long(buf, 1) * 2L;
1562 if (retry > SMTP_RETRY_MAX) {
1563 retry = SMTP_RETRY_MAX;
1565 remove_token(instr, i, '\n');
1567 if (!strcasecmp(key, "attempted")) {
1568 attempted = extract_long(buf, 1);
1569 if (attempted > last_attempted)
1570 last_attempted = attempted;
1575 * Postpone delivery if we've already tried recently.
1577 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1578 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1585 * Bail out if there's no actual message associated with this
1587 if (text_msgid < 0L) {
1588 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1593 /* Plow through the instructions looking for 'remote' directives and
1594 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1595 * were experienced and it's time to try again)
1597 lines = num_tokens(instr, '\n');
1598 for (i=0; i<lines; ++i) {
1599 extract_token(buf, instr, i, '\n', sizeof buf);
1600 extract_token(key, buf, 0, '|', sizeof key);
1601 extract_token(addr, buf, 1, '|', sizeof addr);
1602 status = extract_int(buf, 2);
1603 extract_token(dsn, buf, 3, '|', sizeof dsn);
1604 if ( (!strcasecmp(key, "remote"))
1605 && ((status==0)||(status==3)||(status==4)) ) {
1607 /* Remove this "remote" instruction from the set,
1608 * but replace the set's final newline if
1609 * remove_token() stripped it. It has to be there.
1611 remove_token(instr, i, '\n');
1612 if (instr[strlen(instr)-1] != '\n') {
1613 strcat(instr, "\n");
1618 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1619 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1621 if (results == NULL) {
1622 results = malloc(1024);
1623 memset(results, 0, 1024);
1626 results = realloc(results,
1627 strlen(results) + 1024);
1629 snprintf(&results[strlen(results)], 1024,
1631 key, addr, status, dsn);
1636 if (results != NULL) {
1637 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1638 strcat(instr, results);
1643 /* Generate 'bounce' messages */
1644 smtp_do_bounce(instr);
1646 /* Go through the delivery list, deleting completed deliveries */
1647 incomplete_deliveries_remaining =
1648 smtp_purge_completed_deliveries(instr);
1652 * No delivery instructions remain, so delete both the instructions
1653 * message and the message message.
1655 if (incomplete_deliveries_remaining <= 0) {
1657 delmsgs[0] = msgnum;
1658 delmsgs[1] = text_msgid;
1659 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
1663 * Uncompleted delivery instructions remain, so delete the old
1664 * instructions and replace with the updated ones.
1666 if (incomplete_deliveries_remaining > 0) {
1667 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
1668 msg = malloc(sizeof(struct CtdlMessage));
1669 memset(msg, 0, sizeof(struct CtdlMessage));
1670 msg->cm_magic = CTDLMESSAGE_MAGIC;
1671 msg->cm_anon_type = MES_NORMAL;
1672 msg->cm_format_type = FMT_RFC822;
1673 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1674 snprintf(msg->cm_fields['M'],
1676 "Content-type: %s\n\n%s\n"
1679 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1680 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1681 CtdlFreeMessage(msg);
1692 * Run through the queue sending out messages.
1694 void smtp_do_queue(void) {
1695 static int doing_queue = 0;
1698 * This is a simple concurrency check to make sure only one queue run
1699 * is done at a time. We could do this with a mutex, but since we
1700 * don't really require extremely fine granularity here, we'll do it
1701 * with a static variable instead.
1703 if (doing_queue) return;
1707 * Go ahead and run the queue
1709 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1711 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1712 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1715 CtdlForEachMessage(MSGS_ALL, 0L, NULL,
1716 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1718 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1725 /*****************************************************************************/
1726 /* SMTP UTILITY COMMANDS */
1727 /*****************************************************************************/
1729 void cmd_smtp(char *argbuf) {
1736 if (CtdlAccessCheck(ac_aide)) return;
1738 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1740 if (!strcasecmp(cmd, "mx")) {
1741 extract_token(node, argbuf, 1, '|', sizeof node);
1742 num_mxhosts = getmx(buf, node);
1743 cprintf("%d %d MX hosts listed for %s\n",
1744 LISTING_FOLLOWS, num_mxhosts, node);
1745 for (i=0; i<num_mxhosts; ++i) {
1746 extract_token(node, buf, i, '|', sizeof node);
1747 cprintf("%s\n", node);
1753 else if (!strcasecmp(cmd, "runqueue")) {
1755 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1760 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1767 * Initialize the SMTP outbound queue
1769 void smtp_init_spoolout(void) {
1770 struct ctdlroom qrbuf;
1773 * Create the room. This will silently fail if the room already
1774 * exists, and that's perfectly ok, because we want it to exist.
1776 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1779 * Make sure it's set to be a "system room" so it doesn't show up
1780 * in the <K>nown rooms list for Aides.
1782 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1783 qrbuf.QRflags2 |= QR2_SYSTEM;
1791 /*****************************************************************************/
1792 /* MODULE INITIALIZATION STUFF */
1793 /*****************************************************************************/
1795 * This cleanup function blows away the temporary memory used by
1798 void smtp_cleanup_function(void) {
1800 /* Don't do this stuff if this is not an SMTP session! */
1801 if (CC->h_command_function != smtp_command_loop) return;
1803 lprintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1809 const char *CitadelServiceSMTP_MTA="SMTP-MTA";
1810 const char *CitadelServiceSMTPS_MTA="SMTPs-MTA";
1811 const char *CitadelServiceSMTP_MSA="SMTP-MSA";
1812 const char *CitadelServiceSMTP_LMTP="LMTP";
1813 const char *CitadelServiceSMTP_LMTP_UNF="LMTP-UnF";
1815 CTDL_MODULE_INIT(smtp)
1817 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1822 CitadelServiceSMTP_MTA);
1825 CtdlRegisterServiceHook(config.c_smtps_port,
1830 CitadelServiceSMTPS_MTA);
1833 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1838 CitadelServiceSMTP_MSA);
1840 CtdlRegisterServiceHook(0, /* local LMTP */
1845 CitadelServiceSMTP_LMTP);
1847 CtdlRegisterServiceHook(0, /* local LMTP */
1848 file_lmtp_unfiltered_socket,
1849 lmtp_unfiltered_greeting,
1852 CitadelServiceSMTP_LMTP_UNF);
1854 smtp_init_spoolout();
1855 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1856 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1857 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1859 /* return our Subversion id for the Log */