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);
169 if (!CC->redirect_ssl) CC->kill_me = 1; /* kill session if no crypto */
175 * SMTP MSA port requires authentication.
177 void smtp_msa_greeting(void) {
183 * LMTP is like SMTP but with some extra bonus footage added.
185 void lmtp_greeting(void) {
192 * Generic SMTP MTA greeting
194 void smtp_mta_greeting(void) {
200 * We also have an unfiltered LMTP socket that bypasses spam filters.
202 void lmtp_unfiltered_greeting(void) {
205 SMTP->is_unfiltered = 1;
210 * Login greeting common to all auth methods
212 void smtp_auth_greeting(void) {
213 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
214 lprintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
215 CC->internal_pgm = 0;
216 CC->cs_flags &= ~CS_STEALTH;
221 * Implement HELO and EHLO commands.
223 * which_command: 0=HELO, 1=EHLO, 2=LHLO
225 void smtp_hello(char *argbuf, int which_command) {
227 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
229 if ( (which_command != 2) && (SMTP->is_lmtp) ) {
230 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
234 if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
235 cprintf("500 LHLO is only allowed when running LMTP\r\n");
239 if (which_command == 0) {
240 cprintf("250 Hello %s (%s [%s])\r\n",
247 if (which_command == 1) {
248 cprintf("250-Hello %s (%s [%s])\r\n",
255 cprintf("250-Greetings and joyous salutations.\r\n");
257 cprintf("250-HELP\r\n");
258 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
262 /* Only offer the PIPELINING command if TLS is inactive,
263 * because of flow control issues. Also, avoid offering TLS
264 * if TLS is already active. Finally, we only offer TLS on
265 * the SMTP-MSA port, not on the SMTP-MTA port, due to
266 * questionable reliability of TLS in certain sending MTA's.
268 if ( (!CC->redirect_ssl) && (SMTP->is_msa) ) {
269 cprintf("250-PIPELINING\r\n");
270 cprintf("250-STARTTLS\r\n");
273 #else /* HAVE_OPENSSL */
275 /* Non SSL enabled server, so always offer PIPELINING. */
276 cprintf("250-PIPELINING\r\n");
278 #endif /* HAVE_OPENSSL */
280 cprintf("250-AUTH LOGIN PLAIN\r\n");
281 cprintf("250-AUTH=LOGIN PLAIN\r\n");
283 cprintf("250 ENHANCEDSTATUSCODES\r\n");
290 * Implement HELP command.
292 void smtp_help(void) {
293 cprintf("214-Commands accepted:\r\n");
294 cprintf("214- DATA\r\n");
295 cprintf("214- EHLO\r\n");
296 cprintf("214- HELO\r\n");
297 cprintf("214- HELP\r\n");
298 cprintf("214- MAIL\r\n");
299 cprintf("214- NOOP\r\n");
300 cprintf("214- QUIT\r\n");
301 cprintf("214- RCPT\r\n");
302 cprintf("214- RSET\r\n");
310 void smtp_get_user(char *argbuf) {
314 CtdlDecodeBase64(username, argbuf, SIZ);
315 /* lprintf(CTDL_DEBUG, "Trying <%s>\n", username); */
316 if (CtdlLoginExistingUser(NULL, username) == login_ok) {
317 CtdlEncodeBase64(buf, "Password:", 9);
318 cprintf("334 %s\r\n", buf);
319 SMTP->command_state = smtp_password;
322 cprintf("500 5.7.0 No such user.\r\n");
323 SMTP->command_state = smtp_command;
331 void smtp_get_pass(char *argbuf) {
334 CtdlDecodeBase64(password, argbuf, SIZ);
335 /* lprintf(CTDL_DEBUG, "Trying <%s>\n", password); */
336 if (CtdlTryPassword(password) == pass_ok) {
337 smtp_auth_greeting();
340 cprintf("535 5.7.0 Authentication failed.\r\n");
342 SMTP->command_state = smtp_command;
347 * Back end for PLAIN auth method (either inline or multistate)
349 void smtp_try_plain(char *encoded_authstring) {
350 char decoded_authstring[1024];
356 CtdlDecodeBase64(decoded_authstring, encoded_authstring, strlen(encoded_authstring) );
357 safestrncpy(ident, decoded_authstring, sizeof ident);
358 safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
359 safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
361 SMTP->command_state = smtp_command;
363 if (!IsEmptyStr(ident)) {
364 result = CtdlLoginExistingUser(user, ident);
367 result = CtdlLoginExistingUser(NULL, user);
370 if (result == login_ok) {
371 if (CtdlTryPassword(pass) == pass_ok) {
372 smtp_auth_greeting();
376 cprintf("504 5.7.4 Authentication failed.\r\n");
381 * Attempt to perform authenticated SMTP
383 void smtp_auth(char *argbuf) {
384 char username_prompt[64];
386 char encoded_authstring[1024];
389 cprintf("504 5.7.4 Already logged in.\r\n");
393 extract_token(method, argbuf, 0, ' ', sizeof method);
395 if (!strncasecmp(method, "login", 5) ) {
396 if (strlen(argbuf) >= 7) {
397 smtp_get_user(&argbuf[6]);
400 CtdlEncodeBase64(username_prompt, "Username:", 9);
401 cprintf("334 %s\r\n", username_prompt);
402 SMTP->command_state = smtp_user;
407 if (!strncasecmp(method, "plain", 5) ) {
408 if (num_tokens(argbuf, ' ') < 2) {
410 SMTP->command_state = smtp_plain;
414 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
416 smtp_try_plain(encoded_authstring);
420 if (strncasecmp(method, "login", 5) ) {
421 cprintf("504 5.7.4 Unknown authentication method.\r\n");
429 * Implements the RSET (reset state) command.
430 * Currently this just zeroes out the state buffer. If pointers to data
431 * allocated with malloc() are ever placed in the state buffer, we have to
432 * be sure to free() them first!
434 * Set do_response to nonzero to output the SMTP RSET response code.
436 void smtp_rset(int do_response) {
441 * Our entire SMTP state is discarded when a RSET command is issued,
442 * but we need to preserve this one little piece of information, so
443 * we save it for later.
445 is_lmtp = SMTP->is_lmtp;
446 is_unfiltered = SMTP->is_unfiltered;
448 memset(SMTP, 0, sizeof(struct citsmtp));
451 * It is somewhat ambiguous whether we want to log out when a RSET
452 * command is issued. Here's the code to do it. It is commented out
453 * because some clients (such as Pine) issue RSET commands before
454 * each message, but still expect to be logged in.
456 * if (CC->logged_in) {
462 * Reinstate this little piece of information we saved (see above).
464 SMTP->is_lmtp = is_lmtp;
465 SMTP->is_unfiltered = is_unfiltered;
468 cprintf("250 2.0.0 Zap!\r\n");
473 * Clear out the portions of the state buffer that need to be cleared out
474 * after the DATA command finishes.
476 void smtp_data_clear(void) {
477 strcpy(SMTP->from, "");
478 strcpy(SMTP->recipients, "");
479 SMTP->number_of_recipients = 0;
480 SMTP->delivery_mode = 0;
481 SMTP->message_originated_locally = 0;
487 * Implements the "MAIL From:" command
489 void smtp_mail(char *argbuf) {
494 if (!IsEmptyStr(SMTP->from)) {
495 cprintf("503 5.1.0 Only one sender permitted\r\n");
499 if (strncasecmp(argbuf, "From:", 5)) {
500 cprintf("501 5.1.7 Syntax error\r\n");
504 strcpy(SMTP->from, &argbuf[5]);
506 if (haschar(SMTP->from, '<') > 0) {
507 stripallbut(SMTP->from, '<', '>');
510 /* We used to reject empty sender names, until it was brought to our
511 * attention that RFC1123 5.2.9 requires that this be allowed. So now
512 * we allow it, but replace the empty string with a fake
513 * address so we don't have to contend with the empty string causing
514 * other code to fail when it's expecting something there.
516 if (IsEmptyStr(SMTP->from)) {
517 strcpy(SMTP->from, "someone@somewhere.org");
520 /* If this SMTP connection is from a logged-in user, force the 'from'
521 * to be the user's Internet e-mail address as Citadel knows it.
524 safestrncpy(SMTP->from, CC->cs_inet_email, sizeof SMTP->from);
525 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
526 SMTP->message_originated_locally = 1;
530 else if (SMTP->is_lmtp) {
531 /* Bypass forgery checking for LMTP */
534 /* Otherwise, make sure outsiders aren't trying to forge mail from
535 * this system (unless, of course, c_allow_spoofing is enabled)
537 else if (config.c_allow_spoofing == 0) {
538 process_rfc822_addr(SMTP->from, user, node, name);
539 if (CtdlHostAlias(node) != hostalias_nomatch) {
541 "You must log in to send mail from %s\r\n",
543 strcpy(SMTP->from, "");
548 cprintf("250 2.0.0 Sender ok\r\n");
554 * Implements the "RCPT To:" command
556 void smtp_rcpt(char *argbuf) {
558 char message_to_spammer[SIZ];
559 struct recptypes *valid = NULL;
561 if (IsEmptyStr(SMTP->from)) {
562 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
566 if (strncasecmp(argbuf, "To:", 3)) {
567 cprintf("501 5.1.7 Syntax error\r\n");
571 if ( (SMTP->is_msa) && (!CC->logged_in) ) {
573 "You must log in to send mail on this port.\r\n");
574 strcpy(SMTP->from, "");
578 safestrncpy(recp, &argbuf[3], sizeof recp);
580 stripallbut(recp, '<', '>');
582 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
583 cprintf("452 4.5.3 Too many recipients\r\n");
588 if ( (!CC->logged_in) /* Don't RBL authenticated users */
589 && (!SMTP->is_lmtp) ) { /* Don't RBL LMTP clients */
590 if (config.c_rbl_at_greeting == 0) { /* Don't RBL again if we already did it */
591 if (rbl_check(message_to_spammer)) {
592 cprintf("550 %s\r\n", message_to_spammer);
593 /* no need to free_recipients(valid), it's not allocated yet */
599 valid = validate_recipients(recp);
600 if (valid->num_error != 0) {
601 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
602 free_recipients(valid);
606 if (valid->num_internet > 0) {
608 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
609 cprintf("551 5.7.1 <%s> - you do not have permission to send Internet mail\r\n", recp);
610 free_recipients(valid);
616 if (valid->num_internet > 0) {
617 if ( (SMTP->message_originated_locally == 0)
618 && (SMTP->is_lmtp == 0) ) {
619 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
620 free_recipients(valid);
625 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
626 if (!IsEmptyStr(SMTP->recipients)) {
627 strcat(SMTP->recipients, ",");
629 strcat(SMTP->recipients, recp);
630 SMTP->number_of_recipients += 1;
632 free_recipients(valid);
640 * Implements the DATA command
642 void smtp_data(void) {
644 struct CtdlMessage *msg = NULL;
647 struct recptypes *valid;
652 if (IsEmptyStr(SMTP->from)) {
653 cprintf("503 5.5.1 Need MAIL command first.\r\n");
657 if (SMTP->number_of_recipients < 1) {
658 cprintf("503 5.5.1 Need RCPT command first.\r\n");
662 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
664 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
667 if (body != NULL) snprintf(body, 4096,
668 "Received: from %s (%s [%s])\n"
676 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
679 "Unable to save message: internal error.\r\n");
683 lprintf(CTDL_DEBUG, "Converting message...\n");
684 msg = convert_internet_message(body);
686 /* If the user is locally authenticated, FORCE the From: header to
687 * show up as the real sender. Yes, this violates the RFC standard,
688 * but IT MAKES SENSE. If you prefer strict RFC adherence over
689 * common sense, you can disable this in the configuration.
691 * We also set the "message room name" ('O' field) to MAILROOM
692 * (which is Mail> on most systems) to prevent it from getting set
693 * to something ugly like "0000058008.Sent Items>" when the message
694 * is read with a Citadel client.
696 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
697 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
698 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
699 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
700 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
701 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
702 msg->cm_fields['A'] = strdup(CC->user.fullname);
703 msg->cm_fields['N'] = strdup(config.c_nodename);
704 msg->cm_fields['H'] = strdup(config.c_humannode);
705 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
706 msg->cm_fields['O'] = strdup(MAILROOM);
709 /* Set the "envelope from" address */
710 if (msg->cm_fields['P'] != NULL) {
711 free(msg->cm_fields['P']);
713 msg->cm_fields['P'] = strdup(SMTP->from);
715 /* Set the "envelope to" address */
716 if (msg->cm_fields['V'] != NULL) {
717 free(msg->cm_fields['V']);
719 msg->cm_fields['V'] = strdup(SMTP->recipients);
721 /* Submit the message into the Citadel system. */
722 valid = validate_recipients(SMTP->recipients);
724 /* If there are modules that want to scan this message before final
725 * submission (such as virus checkers or spam filters), call them now
726 * and give them an opportunity to reject the message.
728 if (SMTP->is_unfiltered) {
732 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
735 if (scan_errors > 0) { /* We don't want this message! */
737 if (msg->cm_fields['0'] == NULL) {
738 msg->cm_fields['0'] = strdup(
739 "5.7.1 Message rejected by filter");
742 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
745 else { /* Ok, we'll accept this message. */
746 msgnum = CtdlSubmitMsg(msg, valid, "");
748 sprintf(result, "250 2.0.0 Message accepted.\r\n");
751 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
755 /* For SMTP and ESTMP, just print the result message. For LMTP, we
756 * have to print one result message for each recipient. Since there
757 * is nothing in Citadel which would cause different recipients to
758 * have different results, we can get away with just spitting out the
759 * same message once for each recipient.
762 for (i=0; i<SMTP->number_of_recipients; ++i) {
763 cprintf("%s", result);
767 cprintf("%s", result);
770 /* Write something to the syslog (which may or may not be where the
771 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
774 syslog((LOG_MAIL | LOG_INFO),
775 "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
778 SMTP->number_of_recipients,
786 CtdlFreeMessage(msg);
787 free_recipients(valid);
788 smtp_data_clear(); /* clear out the buffers now */
793 * implements the STARTTLS command (Citadel API version)
795 void smtp_starttls(void)
797 char ok_response[SIZ];
798 char nosup_response[SIZ];
799 char error_response[SIZ];
802 "220 2.0.0 Begin TLS negotiation now\r\n");
803 sprintf(nosup_response,
804 "554 5.7.3 TLS not supported here\r\n");
805 sprintf(error_response,
806 "554 5.7.3 Internal error\r\n");
807 CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
814 * Main command loop for SMTP sessions.
816 void smtp_command_loop(void) {
820 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
821 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
822 lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
826 lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
827 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
829 if (SMTP->command_state == smtp_user) {
830 smtp_get_user(cmdbuf);
833 else if (SMTP->command_state == smtp_password) {
834 smtp_get_pass(cmdbuf);
837 else if (SMTP->command_state == smtp_plain) {
838 smtp_try_plain(cmdbuf);
841 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
842 smtp_auth(&cmdbuf[5]);
845 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
849 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
850 smtp_hello(&cmdbuf[5], 0);
853 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
854 smtp_hello(&cmdbuf[5], 1);
857 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
858 smtp_hello(&cmdbuf[5], 2);
861 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
865 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
866 smtp_mail(&cmdbuf[5]);
869 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
870 cprintf("250 NOOP\r\n");
873 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
874 cprintf("221 Goodbye...\r\n");
879 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
880 smtp_rcpt(&cmdbuf[5]);
883 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
887 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
892 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
901 /*****************************************************************************/
902 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
903 /*****************************************************************************/
910 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
913 void smtp_try(const char *key, const char *addr, int *status,
914 char *dsn, size_t n, long msgnum)
921 char user[1024], node[1024], name[1024];
934 /* Parse out the host portion of the recipient address */
935 process_rfc822_addr(addr, user, node, name);
937 lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
940 /* Load the message out of the database */
941 CC->redirect_buffer = malloc(SIZ);
942 CC->redirect_len = 0;
943 CC->redirect_alloc = SIZ;
944 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
945 msgtext = CC->redirect_buffer;
946 msg_size = CC->redirect_len;
947 CC->redirect_buffer = NULL;
948 CC->redirect_len = 0;
949 CC->redirect_alloc = 0;
951 /* Extract something to send later in the 'MAIL From:' command */
952 strcpy(mailfrom, "");
956 if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
959 if (!strncasecmp(buf, "From:", 5)) {
960 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
962 for (i=0; mailfrom[i]; ++i) {
963 if (!isprint(mailfrom[i])) {
964 strcpy(&mailfrom[i], &mailfrom[i+1]);
969 /* Strip out parenthesized names */
972 for (i=0; mailfrom[i]; ++i) {
973 if (mailfrom[i] == '(') lp = i;
974 if (mailfrom[i] == ')') rp = i;
976 if ((lp>0)&&(rp>lp)) {
977 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
980 /* Prefer brokketized names */
983 for (i=0; mailfrom[i]; ++i) {
984 if (mailfrom[i] == '<') lp = i;
985 if (mailfrom[i] == '>') rp = i;
987 if ( (lp>=0) && (rp>lp) ) {
989 strcpy(mailfrom, &mailfrom[lp]);
994 } while (scan_done == 0);
995 if (IsEmptyStr(mailfrom)) strcpy(mailfrom, "someone@somewhere.org");
996 stripallbut(mailfrom, '<', '>');
998 /* Figure out what mail exchanger host we have to connect to */
999 num_mxhosts = getmx(mxhosts, node);
1000 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
1001 if (num_mxhosts < 1) {
1003 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
1008 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
1010 extract_token(buf, mxhosts, mx, '|', sizeof buf);
1011 strcpy(mx_user, "");
1012 strcpy(mx_pass, "");
1013 if (num_tokens(buf, '@') > 1) {
1014 strcpy (mx_user, buf);
1015 endpart = strrchr(mx_user, '@');
1017 strcpy (mx_host, endpart + 1);
1018 endpart = strrchr(mx_user, ':');
1019 if (endpart != NULL) {
1020 strcpy(mx_pass, endpart+1);
1025 strcpy (mx_host, buf);
1026 endpart = strrchr(mx_host, ':');
1029 strcpy(mx_port, endpart + 1);
1032 strcpy(mx_port, "25");
1034 lprintf(CTDL_DEBUG, "Trying %s : %s ...\n", mx_host, mx_port);
1035 sock = sock_connect(mx_host, mx_port, "tcp");
1036 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
1037 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
1040 snprintf(dsn, SIZ, "%s", strerror(errno));
1043 snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
1049 *status = 4; /* dsn is already filled in */
1053 /* Process the SMTP greeting from the server */
1054 if (ml_sock_gets(sock, buf) < 0) {
1056 strcpy(dsn, "Connection broken during SMTP conversation");
1059 lprintf(CTDL_DEBUG, "<%s\n", buf);
1060 if (buf[0] != '2') {
1061 if (buf[0] == '4') {
1063 safestrncpy(dsn, &buf[4], 1023);
1068 safestrncpy(dsn, &buf[4], 1023);
1073 /* At this point we know we are talking to a real SMTP server */
1075 /* Do a EHLO command. If it fails, try the HELO command. */
1076 snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
1077 lprintf(CTDL_DEBUG, ">%s", buf);
1078 sock_write(sock, buf, strlen(buf));
1079 if (ml_sock_gets(sock, buf) < 0) {
1081 strcpy(dsn, "Connection broken during SMTP HELO");
1084 lprintf(CTDL_DEBUG, "<%s\n", buf);
1085 if (buf[0] != '2') {
1086 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1087 lprintf(CTDL_DEBUG, ">%s", buf);
1088 sock_write(sock, buf, strlen(buf));
1089 if (ml_sock_gets(sock, buf) < 0) {
1091 strcpy(dsn, "Connection broken during SMTP HELO");
1095 if (buf[0] != '2') {
1096 if (buf[0] == '4') {
1098 safestrncpy(dsn, &buf[4], 1023);
1103 safestrncpy(dsn, &buf[4], 1023);
1108 /* Do an AUTH command if necessary */
1109 if (!IsEmptyStr(mx_user)) {
1111 sprintf(buf, "%s%c%s%c%s", mx_user, '\0', mx_user, '\0', mx_pass);
1112 CtdlEncodeBase64(encoded, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 2);
1113 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", encoded);
1114 lprintf(CTDL_DEBUG, ">%s", buf);
1115 sock_write(sock, buf, strlen(buf));
1116 if (ml_sock_gets(sock, buf) < 0) {
1118 strcpy(dsn, "Connection broken during SMTP AUTH");
1121 lprintf(CTDL_DEBUG, "<%s\n", buf);
1122 if (buf[0] != '2') {
1123 if (buf[0] == '4') {
1125 safestrncpy(dsn, &buf[4], 1023);
1130 safestrncpy(dsn, &buf[4], 1023);
1136 /* previous command succeeded, now try the MAIL From: command */
1137 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1138 lprintf(CTDL_DEBUG, ">%s", buf);
1139 sock_write(sock, buf, strlen(buf));
1140 if (ml_sock_gets(sock, buf) < 0) {
1142 strcpy(dsn, "Connection broken during SMTP MAIL");
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 /* MAIL succeeded, now try the RCPT To: command */
1160 snprintf(buf, sizeof buf, "RCPT To: <%s@%s>\r\n", user, node);
1161 lprintf(CTDL_DEBUG, ">%s", buf);
1162 sock_write(sock, buf, strlen(buf));
1163 if (ml_sock_gets(sock, buf) < 0) {
1165 strcpy(dsn, "Connection broken during SMTP RCPT");
1168 lprintf(CTDL_DEBUG, "<%s\n", buf);
1169 if (buf[0] != '2') {
1170 if (buf[0] == '4') {
1172 safestrncpy(dsn, &buf[4], 1023);
1177 safestrncpy(dsn, &buf[4], 1023);
1182 /* RCPT succeeded, now try the DATA command */
1183 lprintf(CTDL_DEBUG, ">DATA\n");
1184 sock_write(sock, "DATA\r\n", 6);
1185 if (ml_sock_gets(sock, buf) < 0) {
1187 strcpy(dsn, "Connection broken during SMTP DATA");
1190 lprintf(CTDL_DEBUG, "<%s\n", buf);
1191 if (buf[0] != '3') {
1192 if (buf[0] == '4') {
1194 safestrncpy(dsn, &buf[4], 1023);
1199 safestrncpy(dsn, &buf[4], 1023);
1204 /* If we reach this point, the server is expecting data */
1205 sock_write(sock, msgtext, msg_size);
1206 if (msgtext[msg_size-1] != 10) {
1207 lprintf(CTDL_WARNING, "Possible problem: message did not "
1208 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1212 sock_write(sock, ".\r\n", 3);
1213 if (ml_sock_gets(sock, buf) < 0) {
1215 strcpy(dsn, "Connection broken during SMTP message transmit");
1218 lprintf(CTDL_DEBUG, "%s\n", buf);
1219 if (buf[0] != '2') {
1220 if (buf[0] == '4') {
1222 safestrncpy(dsn, &buf[4], 1023);
1227 safestrncpy(dsn, &buf[4], 1023);
1233 safestrncpy(dsn, &buf[4], 1023);
1236 lprintf(CTDL_DEBUG, ">QUIT\n");
1237 sock_write(sock, "QUIT\r\n", 6);
1238 ml_sock_gets(sock, buf);
1239 lprintf(CTDL_DEBUG, "<%s\n", buf);
1240 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1243 bail: free(msgtext);
1246 /* Write something to the syslog (which may or may not be where the
1247 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1249 if (enable_syslog) {
1250 syslog((LOG_MAIL | LOG_INFO),
1251 "%ld: to=<%s>, relay=%s, stat=%s",
1265 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1266 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1267 * a "bounce" message (delivery status notification).
1269 void smtp_do_bounce(char *instr) {
1277 char bounceto[1024];
1279 int num_bounces = 0;
1280 int bounce_this = 0;
1281 long bounce_msgid = (-1);
1282 time_t submitted = 0L;
1283 struct CtdlMessage *bmsg = NULL;
1285 struct recptypes *valid;
1286 int successful_bounce = 0;
1292 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1293 strcpy(bounceto, "");
1294 sprintf(boundary, "=_Citadel_Multipart_%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
1295 lines = num_tokens(instr, '\n');
1297 /* See if it's time to give up on delivery of this message */
1298 for (i=0; i<lines; ++i) {
1299 extract_token(buf, instr, i, '\n', sizeof buf);
1300 extract_token(key, buf, 0, '|', sizeof key);
1301 extract_token(addr, buf, 1, '|', sizeof addr);
1302 if (!strcasecmp(key, "submitted")) {
1303 submitted = atol(addr);
1307 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1311 /* Start building our bounce message */
1313 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1314 if (bmsg == NULL) return;
1315 memset(bmsg, 0, sizeof(struct CtdlMessage));
1317 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1318 bmsg->cm_anon_type = MES_NORMAL;
1319 bmsg->cm_format_type = FMT_RFC822;
1320 bmsg->cm_fields['A'] = strdup("Citadel");
1321 bmsg->cm_fields['O'] = strdup(MAILROOM);
1322 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1323 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1324 bmsg->cm_fields['M'] = malloc(1024);
1326 strcpy(bmsg->cm_fields['M'], "Content-type: multipart/mixed; boundary=\"");
1327 strcat(bmsg->cm_fields['M'], boundary);
1328 strcat(bmsg->cm_fields['M'], "\"\r\n");
1329 strcat(bmsg->cm_fields['M'], "MIME-Version: 1.0\r\n");
1330 strcat(bmsg->cm_fields['M'], "X-Mailer: " CITADEL "\r\n");
1331 strcat(bmsg->cm_fields['M'], "\r\nThis is a multipart message in MIME format.\r\n\r\n");
1332 strcat(bmsg->cm_fields['M'], "--");
1333 strcat(bmsg->cm_fields['M'], boundary);
1334 strcat(bmsg->cm_fields['M'], "\r\n");
1335 strcat(bmsg->cm_fields['M'], "Content-type: text/plain\r\n\r\n");
1337 if (give_up) strcat(bmsg->cm_fields['M'],
1338 "A message you sent could not be delivered to some or all of its recipients\n"
1339 "due to prolonged unavailability of its destination(s).\n"
1340 "Giving up on the following addresses:\n\n"
1343 else strcat(bmsg->cm_fields['M'],
1344 "A message you sent could not be delivered to some or all of its recipients.\n"
1345 "The following addresses were undeliverable:\n\n"
1349 * Now go through the instructions checking for stuff.
1351 for (i=0; i<lines; ++i) {
1352 extract_token(buf, instr, i, '\n', sizeof buf);
1353 extract_token(key, buf, 0, '|', sizeof key);
1354 extract_token(addr, buf, 1, '|', sizeof addr);
1355 status = extract_int(buf, 2);
1356 extract_token(dsn, buf, 3, '|', sizeof dsn);
1359 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1360 key, addr, status, dsn);
1362 if (!strcasecmp(key, "bounceto")) {
1363 strcpy(bounceto, addr);
1366 if (!strcasecmp(key, "msgid")) {
1367 omsgid = atol(addr);
1370 if (!strcasecmp(key, "remote")) {
1371 if (status == 5) bounce_this = 1;
1372 if (give_up) bounce_this = 1;
1378 if (bmsg->cm_fields['M'] == NULL) {
1379 lprintf(CTDL_ERR, "ERROR ... M field is null "
1380 "(%s:%d)\n", __FILE__, __LINE__);
1383 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1384 strlen(bmsg->cm_fields['M']) + 1024 );
1385 strcat(bmsg->cm_fields['M'], addr);
1386 strcat(bmsg->cm_fields['M'], ": ");
1387 strcat(bmsg->cm_fields['M'], dsn);
1388 strcat(bmsg->cm_fields['M'], "\r\n");
1390 remove_token(instr, i, '\n');
1396 /* Attach the original message */
1398 strcat(bmsg->cm_fields['M'], "--");
1399 strcat(bmsg->cm_fields['M'], boundary);
1400 strcat(bmsg->cm_fields['M'], "\r\n");
1401 strcat(bmsg->cm_fields['M'], "Content-type: message/rfc822\r\n");
1402 strcat(bmsg->cm_fields['M'], "Content-Transfer-Encoding: 7bit\r\n");
1403 strcat(bmsg->cm_fields['M'], "Content-Disposition: inline\r\n");
1404 strcat(bmsg->cm_fields['M'], "\r\n");
1406 CC->redirect_buffer = malloc(SIZ);
1407 CC->redirect_len = 0;
1408 CC->redirect_alloc = SIZ;
1409 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
1410 omsgtext = CC->redirect_buffer;
1411 omsgsize = CC->redirect_len;
1412 CC->redirect_buffer = NULL;
1413 CC->redirect_len = 0;
1414 CC->redirect_alloc = 0;
1415 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1416 (strlen(bmsg->cm_fields['M']) + omsgsize + 1024) );
1417 strcat(bmsg->cm_fields['M'], omsgtext);
1421 /* Close the multipart MIME scope */
1422 strcat(bmsg->cm_fields['M'], "--");
1423 strcat(bmsg->cm_fields['M'], boundary);
1424 strcat(bmsg->cm_fields['M'], "--\r\n");
1426 /* Deliver the bounce if there's anything worth mentioning */
1427 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1428 if (num_bounces > 0) {
1430 /* First try the user who sent the message */
1431 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1432 if (IsEmptyStr(bounceto)) {
1433 lprintf(CTDL_ERR, "No bounce address specified\n");
1434 bounce_msgid = (-1L);
1437 /* Can we deliver the bounce to the original sender? */
1438 valid = validate_recipients(bounceto);
1439 if (valid != NULL) {
1440 if (valid->num_error == 0) {
1441 CtdlSubmitMsg(bmsg, valid, "");
1442 successful_bounce = 1;
1446 /* If not, post it in the Aide> room */
1447 if (successful_bounce == 0) {
1448 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1451 /* Free up the memory we used */
1452 if (valid != NULL) {
1453 free_recipients(valid);
1457 CtdlFreeMessage(bmsg);
1458 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1463 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1464 * set of delivery instructions for completed deliveries and remove them.
1466 * It returns the number of incomplete deliveries remaining.
1468 int smtp_purge_completed_deliveries(char *instr) {
1479 lines = num_tokens(instr, '\n');
1480 for (i=0; i<lines; ++i) {
1481 extract_token(buf, instr, i, '\n', sizeof buf);
1482 extract_token(key, buf, 0, '|', sizeof key);
1483 extract_token(addr, buf, 1, '|', sizeof addr);
1484 status = extract_int(buf, 2);
1485 extract_token(dsn, buf, 3, '|', sizeof dsn);
1489 if (!strcasecmp(key, "remote")) {
1490 if (status == 2) completed = 1;
1495 remove_token(instr, i, '\n');
1508 * Called by smtp_do_queue() to handle an individual message.
1510 void smtp_do_procmsg(long msgnum, void *userdata) {
1511 struct CtdlMessage *msg = NULL;
1513 char *results = NULL;
1521 long text_msgid = (-1);
1522 int incomplete_deliveries_remaining;
1523 time_t attempted = 0L;
1524 time_t last_attempted = 0L;
1525 time_t retry = SMTP_RETRY_INTERVAL;
1527 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1529 msg = CtdlFetchMessage(msgnum, 1);
1531 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1535 instr = strdup(msg->cm_fields['M']);
1536 CtdlFreeMessage(msg);
1538 /* Strip out the headers amd any other non-instruction line */
1539 lines = num_tokens(instr, '\n');
1540 for (i=0; i<lines; ++i) {
1541 extract_token(buf, instr, i, '\n', sizeof buf);
1542 if (num_tokens(buf, '|') < 2) {
1543 remove_token(instr, i, '\n');
1549 /* Learn the message ID and find out about recent delivery attempts */
1550 lines = num_tokens(instr, '\n');
1551 for (i=0; i<lines; ++i) {
1552 extract_token(buf, instr, i, '\n', sizeof buf);
1553 extract_token(key, buf, 0, '|', sizeof key);
1554 if (!strcasecmp(key, "msgid")) {
1555 text_msgid = extract_long(buf, 1);
1557 if (!strcasecmp(key, "retry")) {
1558 /* double the retry interval after each attempt */
1559 retry = extract_long(buf, 1) * 2L;
1560 if (retry > SMTP_RETRY_MAX) {
1561 retry = SMTP_RETRY_MAX;
1563 remove_token(instr, i, '\n');
1565 if (!strcasecmp(key, "attempted")) {
1566 attempted = extract_long(buf, 1);
1567 if (attempted > last_attempted)
1568 last_attempted = attempted;
1573 * Postpone delivery if we've already tried recently.
1575 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1576 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1583 * Bail out if there's no actual message associated with this
1585 if (text_msgid < 0L) {
1586 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1591 /* Plow through the instructions looking for 'remote' directives and
1592 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1593 * were experienced and it's time to try again)
1595 lines = num_tokens(instr, '\n');
1596 for (i=0; i<lines; ++i) {
1597 extract_token(buf, instr, i, '\n', sizeof buf);
1598 extract_token(key, buf, 0, '|', sizeof key);
1599 extract_token(addr, buf, 1, '|', sizeof addr);
1600 status = extract_int(buf, 2);
1601 extract_token(dsn, buf, 3, '|', sizeof dsn);
1602 if ( (!strcasecmp(key, "remote"))
1603 && ((status==0)||(status==3)||(status==4)) ) {
1605 /* Remove this "remote" instruction from the set,
1606 * but replace the set's final newline if
1607 * remove_token() stripped it. It has to be there.
1609 remove_token(instr, i, '\n');
1610 if (instr[strlen(instr)-1] != '\n') {
1611 strcat(instr, "\n");
1616 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1617 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1619 if (results == NULL) {
1620 results = malloc(1024);
1621 memset(results, 0, 1024);
1624 results = realloc(results,
1625 strlen(results) + 1024);
1627 snprintf(&results[strlen(results)], 1024,
1629 key, addr, status, dsn);
1634 if (results != NULL) {
1635 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1636 strcat(instr, results);
1641 /* Generate 'bounce' messages */
1642 smtp_do_bounce(instr);
1644 /* Go through the delivery list, deleting completed deliveries */
1645 incomplete_deliveries_remaining =
1646 smtp_purge_completed_deliveries(instr);
1650 * No delivery instructions remain, so delete both the instructions
1651 * message and the message message.
1653 if (incomplete_deliveries_remaining <= 0) {
1655 delmsgs[0] = msgnum;
1656 delmsgs[1] = text_msgid;
1657 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
1661 * Uncompleted delivery instructions remain, so delete the old
1662 * instructions and replace with the updated ones.
1664 if (incomplete_deliveries_remaining > 0) {
1665 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
1666 msg = malloc(sizeof(struct CtdlMessage));
1667 memset(msg, 0, sizeof(struct CtdlMessage));
1668 msg->cm_magic = CTDLMESSAGE_MAGIC;
1669 msg->cm_anon_type = MES_NORMAL;
1670 msg->cm_format_type = FMT_RFC822;
1671 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1672 snprintf(msg->cm_fields['M'],
1674 "Content-type: %s\n\n%s\n"
1677 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1678 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1679 CtdlFreeMessage(msg);
1690 * Run through the queue sending out messages.
1692 void smtp_do_queue(void) {
1693 static int doing_queue = 0;
1696 * This is a simple concurrency check to make sure only one queue run
1697 * is done at a time. We could do this with a mutex, but since we
1698 * don't really require extremely fine granularity here, we'll do it
1699 * with a static variable instead.
1701 if (doing_queue) return;
1705 * Go ahead and run the queue
1707 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1709 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1710 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1713 CtdlForEachMessage(MSGS_ALL, 0L, NULL,
1714 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1716 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1723 /*****************************************************************************/
1724 /* SMTP UTILITY COMMANDS */
1725 /*****************************************************************************/
1727 void cmd_smtp(char *argbuf) {
1734 if (CtdlAccessCheck(ac_aide)) return;
1736 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1738 if (!strcasecmp(cmd, "mx")) {
1739 extract_token(node, argbuf, 1, '|', sizeof node);
1740 num_mxhosts = getmx(buf, node);
1741 cprintf("%d %d MX hosts listed for %s\n",
1742 LISTING_FOLLOWS, num_mxhosts, node);
1743 for (i=0; i<num_mxhosts; ++i) {
1744 extract_token(node, buf, i, '|', sizeof node);
1745 cprintf("%s\n", node);
1751 else if (!strcasecmp(cmd, "runqueue")) {
1753 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1758 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1765 * Initialize the SMTP outbound queue
1767 void smtp_init_spoolout(void) {
1768 struct ctdlroom qrbuf;
1771 * Create the room. This will silently fail if the room already
1772 * exists, and that's perfectly ok, because we want it to exist.
1774 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1777 * Make sure it's set to be a "system room" so it doesn't show up
1778 * in the <K>nown rooms list for Aides.
1780 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1781 qrbuf.QRflags2 |= QR2_SYSTEM;
1789 /*****************************************************************************/
1790 /* MODULE INITIALIZATION STUFF */
1791 /*****************************************************************************/
1793 * This cleanup function blows away the temporary memory used by
1796 void smtp_cleanup_function(void) {
1798 /* Don't do this stuff if this is not an SMTP session! */
1799 if (CC->h_command_function != smtp_command_loop) return;
1801 lprintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1807 const char *CitadelServiceSMTP_MTA="SMTP-MTA";
1808 const char *CitadelServiceSMTPS_MTA="SMTPs-MTA";
1809 const char *CitadelServiceSMTP_MSA="SMTP-MSA";
1810 const char *CitadelServiceSMTP_LMTP="LMTP";
1811 const char *CitadelServiceSMTP_LMTP_UNF="LMTP-UnF";
1813 CTDL_MODULE_INIT(smtp)
1815 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1820 CitadelServiceSMTP_MTA);
1823 CtdlRegisterServiceHook(config.c_smtps_port,
1828 CitadelServiceSMTPS_MTA);
1831 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1836 CitadelServiceSMTP_MSA);
1838 CtdlRegisterServiceHook(0, /* local LMTP */
1843 CitadelServiceSMTP_LMTP);
1845 CtdlRegisterServiceHook(0, /* local LMTP */
1846 file_lmtp_unfiltered_socket,
1847 lmtp_unfiltered_greeting,
1850 CitadelServiceSMTP_LMTP_UNF);
1852 smtp_init_spoolout();
1853 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1854 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1855 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1857 /* return our Subversion id for the Log */