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 strcpy(CC->cs_clientname, "SMTP session");
134 CC->internal_pgm = 1;
135 CC->cs_flags |= CS_STEALTH;
136 SMTP = malloc(sizeof(struct citsmtp));
137 SMTP_RECPS = malloc(SIZ);
138 SMTP_ROOMS = malloc(SIZ);
139 memset(SMTP, 0, sizeof(struct citsmtp));
140 memset(SMTP_RECPS, 0, SIZ);
141 memset(SMTP_ROOMS, 0, SIZ);
143 cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn);
148 * SMTPS is just like SMTP, except it goes crypto right away.
151 void smtps_greeting(void) {
152 CtdlStartTLS(NULL, NULL, NULL);
159 * SMTP MSA port requires authentication.
161 void smtp_msa_greeting(void) {
168 * LMTP is like SMTP but with some extra bonus footage added.
170 void lmtp_greeting(void) {
177 * We also have an unfiltered LMTP socket that bypasses spam filters.
179 void lmtp_unfiltered_greeting(void) {
182 SMTP->is_unfiltered = 1;
187 * Login greeting common to all auth methods
189 void smtp_auth_greeting(void) {
190 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
191 lprintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
192 CC->internal_pgm = 0;
193 CC->cs_flags &= ~CS_STEALTH;
198 * Implement HELO and EHLO commands.
200 * which_command: 0=HELO, 1=EHLO, 2=LHLO
202 void smtp_hello(char *argbuf, int which_command) {
204 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
206 if ( (which_command != 2) && (SMTP->is_lmtp) ) {
207 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
211 if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
212 cprintf("500 LHLO is only allowed when running LMTP\r\n");
216 if (which_command == 0) {
217 cprintf("250 Hello %s (%s [%s])\r\n",
224 if (which_command == 1) {
225 cprintf("250-Hello %s (%s [%s])\r\n",
232 cprintf("250-Greetings and joyous salutations.\r\n");
234 cprintf("250-HELP\r\n");
235 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
239 /* Only offer the PIPELINING command if TLS is inactive,
240 * because of flow control issues. Also, avoid offering TLS
241 * if TLS is already active. Finally, we only offer TLS on
242 * the SMTP-MSA port, not on the SMTP-MTA port, due to
243 * questionable reliability of TLS in certain sending MTA's.
245 if ( (!CC->redirect_ssl) && (SMTP->is_msa) ) {
246 cprintf("250-PIPELINING\r\n");
247 cprintf("250-STARTTLS\r\n");
250 #else /* HAVE_OPENSSL */
252 /* Non SSL enabled server, so always offer PIPELINING. */
253 cprintf("250-PIPELINING\r\n");
255 #endif /* HAVE_OPENSSL */
257 cprintf("250-AUTH LOGIN PLAIN\r\n");
258 cprintf("250-AUTH=LOGIN PLAIN\r\n");
260 cprintf("250 ENHANCEDSTATUSCODES\r\n");
267 * Implement HELP command.
269 void smtp_help(void) {
270 cprintf("214-Commands accepted:\r\n");
271 cprintf("214- DATA\r\n");
272 cprintf("214- EHLO\r\n");
273 cprintf("214- EXPN\r\n");
274 cprintf("214- HELO\r\n");
275 cprintf("214- HELP\r\n");
276 cprintf("214- MAIL\r\n");
277 cprintf("214- NOOP\r\n");
278 cprintf("214- QUIT\r\n");
279 cprintf("214- RCPT\r\n");
280 cprintf("214- RSET\r\n");
281 cprintf("214- VRFY\r\n");
289 void smtp_get_user(char *argbuf) {
293 CtdlDecodeBase64(username, argbuf, SIZ);
294 /* lprintf(CTDL_DEBUG, "Trying <%s>\n", username); */
295 if (CtdlLoginExistingUser(username) == login_ok) {
296 CtdlEncodeBase64(buf, "Password:", 9);
297 cprintf("334 %s\r\n", buf);
298 SMTP->command_state = smtp_password;
301 cprintf("500 5.7.0 No such user.\r\n");
302 SMTP->command_state = smtp_command;
310 void smtp_get_pass(char *argbuf) {
313 CtdlDecodeBase64(password, argbuf, SIZ);
314 /* lprintf(CTDL_DEBUG, "Trying <%s>\n", password); */
315 if (CtdlTryPassword(password) == pass_ok) {
316 smtp_auth_greeting();
319 cprintf("535 5.7.0 Authentication failed.\r\n");
321 SMTP->command_state = smtp_command;
326 * Back end for PLAIN auth method (either inline or multistate)
328 void smtp_try_plain(char *encoded_authstring) {
329 char decoded_authstring[1024];
334 CtdlDecodeBase64(decoded_authstring,
336 strlen(encoded_authstring) );
337 safestrncpy(ident, decoded_authstring, sizeof ident);
338 safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
339 safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
341 SMTP->command_state = smtp_command;
342 if (CtdlLoginExistingUser(user) == login_ok) {
343 if (CtdlTryPassword(pass) == pass_ok) {
344 smtp_auth_greeting();
348 cprintf("504 5.7.4 Authentication failed.\r\n");
353 * Attempt to perform authenticated SMTP
355 void smtp_auth(char *argbuf) {
356 char username_prompt[64];
358 char encoded_authstring[1024];
361 cprintf("504 5.7.4 Already logged in.\r\n");
365 extract_token(method, argbuf, 0, ' ', sizeof method);
367 if (!strncasecmp(method, "login", 5) ) {
368 if (strlen(argbuf) >= 7) {
369 smtp_get_user(&argbuf[6]);
372 CtdlEncodeBase64(username_prompt, "Username:", 9);
373 cprintf("334 %s\r\n", username_prompt);
374 SMTP->command_state = smtp_user;
379 if (!strncasecmp(method, "plain", 5) ) {
380 if (num_tokens(argbuf, ' ') < 2) {
382 SMTP->command_state = smtp_plain;
386 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
388 smtp_try_plain(encoded_authstring);
392 if (strncasecmp(method, "login", 5) ) {
393 cprintf("504 5.7.4 Unknown authentication method.\r\n");
401 * Back end for smtp_vrfy() command
403 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
405 if (!fuzzy_match(us, SMTP->vrfy_match)) {
407 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
413 * Implements the VRFY (verify user name) command.
414 * Performs fuzzy match on full user names.
416 void smtp_vrfy(char *argbuf) {
417 SMTP->vrfy_count = 0;
418 strcpy(SMTP->vrfy_match, argbuf);
419 ForEachUser(smtp_vrfy_backend, NULL);
421 if (SMTP->vrfy_count < 1) {
422 cprintf("550 5.1.1 String does not match anything.\r\n");
424 else if (SMTP->vrfy_count == 1) {
425 cprintf("250 %s <cit%ld@%s>\r\n",
426 SMTP->vrfy_buffer.fullname,
427 SMTP->vrfy_buffer.usernum,
430 else if (SMTP->vrfy_count > 1) {
431 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
440 * Back end for smtp_expn() command
442 void smtp_expn_backend(struct ctdluser *us, void *data) {
444 if (!fuzzy_match(us, SMTP->vrfy_match)) {
446 if (SMTP->vrfy_count >= 1) {
447 cprintf("250-%s <cit%ld@%s>\r\n",
448 SMTP->vrfy_buffer.fullname,
449 SMTP->vrfy_buffer.usernum,
454 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
460 * Implements the EXPN (expand user name) command.
461 * Performs fuzzy match on full user names.
463 void smtp_expn(char *argbuf) {
464 SMTP->vrfy_count = 0;
465 strcpy(SMTP->vrfy_match, argbuf);
466 ForEachUser(smtp_expn_backend, NULL);
468 if (SMTP->vrfy_count < 1) {
469 cprintf("550 5.1.1 String does not match anything.\r\n");
471 else if (SMTP->vrfy_count >= 1) {
472 cprintf("250 %s <cit%ld@%s>\r\n",
473 SMTP->vrfy_buffer.fullname,
474 SMTP->vrfy_buffer.usernum,
481 * Implements the RSET (reset state) command.
482 * Currently this just zeroes out the state buffer. If pointers to data
483 * allocated with malloc() are ever placed in the state buffer, we have to
484 * be sure to free() them first!
486 * Set do_response to nonzero to output the SMTP RSET response code.
488 void smtp_rset(int do_response) {
493 * Our entire SMTP state is discarded when a RSET command is issued,
494 * but we need to preserve this one little piece of information, so
495 * we save it for later.
497 is_lmtp = SMTP->is_lmtp;
498 is_unfiltered = SMTP->is_unfiltered;
500 memset(SMTP, 0, sizeof(struct citsmtp));
503 * It is somewhat ambiguous whether we want to log out when a RSET
504 * command is issued. Here's the code to do it. It is commented out
505 * because some clients (such as Pine) issue RSET commands before
506 * each message, but still expect to be logged in.
508 * if (CC->logged_in) {
514 * Reinstate this little piece of information we saved (see above).
516 SMTP->is_lmtp = is_lmtp;
517 SMTP->is_unfiltered = is_unfiltered;
520 cprintf("250 2.0.0 Zap!\r\n");
525 * Clear out the portions of the state buffer that need to be cleared out
526 * after the DATA command finishes.
528 void smtp_data_clear(void) {
529 strcpy(SMTP->from, "");
530 strcpy(SMTP->recipients, "");
531 SMTP->number_of_recipients = 0;
532 SMTP->delivery_mode = 0;
533 SMTP->message_originated_locally = 0;
539 * Implements the "MAIL From:" command
541 void smtp_mail(char *argbuf) {
546 if (strlen(SMTP->from) != 0) {
547 cprintf("503 5.1.0 Only one sender permitted\r\n");
551 if (strncasecmp(argbuf, "From:", 5)) {
552 cprintf("501 5.1.7 Syntax error\r\n");
556 strcpy(SMTP->from, &argbuf[5]);
558 if (haschar(SMTP->from, '<') > 0) {
559 stripallbut(SMTP->from, '<', '>');
562 /* We used to reject empty sender names, until it was brought to our
563 * attention that RFC1123 5.2.9 requires that this be allowed. So now
564 * we allow it, but replace the empty string with a fake
565 * address so we don't have to contend with the empty string causing
566 * other code to fail when it's expecting something there.
568 if (strlen(SMTP->from) == 0) {
569 strcpy(SMTP->from, "someone@somewhere.org");
572 /* If this SMTP connection is from a logged-in user, force the 'from'
573 * to be the user's Internet e-mail address as Citadel knows it.
576 safestrncpy(SMTP->from, CC->cs_inet_email, sizeof SMTP->from);
577 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
578 SMTP->message_originated_locally = 1;
582 else if (SMTP->is_lmtp) {
583 /* Bypass forgery checking for LMTP */
586 /* Otherwise, make sure outsiders aren't trying to forge mail from
587 * this system (unless, of course, c_allow_spoofing is enabled)
589 else if (config.c_allow_spoofing == 0) {
590 process_rfc822_addr(SMTP->from, user, node, name);
591 if (CtdlHostAlias(node) != hostalias_nomatch) {
593 "You must log in to send mail from %s\r\n",
595 strcpy(SMTP->from, "");
600 cprintf("250 2.0.0 Sender ok\r\n");
606 * Implements the "RCPT To:" command
608 void smtp_rcpt(char *argbuf) {
610 char message_to_spammer[SIZ];
611 struct recptypes *valid = NULL;
613 if (strlen(SMTP->from) == 0) {
614 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
618 if (strncasecmp(argbuf, "To:", 3)) {
619 cprintf("501 5.1.7 Syntax error\r\n");
623 if ( (SMTP->is_msa) && (!CC->logged_in) ) {
625 "You must log in to send mail on this port.\r\n");
626 strcpy(SMTP->from, "");
630 strcpy(recp, &argbuf[3]);
632 stripallbut(recp, '<', '>');
634 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
635 cprintf("452 4.5.3 Too many recipients\r\n");
640 if ( (!CC->logged_in)
641 && (!SMTP->is_lmtp) ) {
642 if (rbl_check(message_to_spammer)) {
643 cprintf("550 %s\r\n", message_to_spammer);
644 /* no need to free(valid), it's not allocated yet */
649 valid = validate_recipients(recp);
650 if (valid->num_error != 0) {
651 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
656 if (valid->num_internet > 0) {
658 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
659 cprintf("551 5.7.1 <%s> - you do not have permission to send Internet mail\r\n", recp);
666 if (valid->num_internet > 0) {
667 if ( (SMTP->message_originated_locally == 0)
668 && (SMTP->is_lmtp == 0) ) {
669 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
675 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
676 if (strlen(SMTP->recipients) > 0) {
677 strcat(SMTP->recipients, ",");
679 strcat(SMTP->recipients, recp);
680 SMTP->number_of_recipients += 1;
687 * Implements the DATA command
689 void smtp_data(void) {
691 struct CtdlMessage *msg;
694 struct recptypes *valid;
699 if (strlen(SMTP->from) == 0) {
700 cprintf("503 5.5.1 Need MAIL command first.\r\n");
704 if (SMTP->number_of_recipients < 1) {
705 cprintf("503 5.5.1 Need RCPT command first.\r\n");
709 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
711 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
714 if (body != NULL) snprintf(body, 4096,
715 "Received: from %s (%s [%s])\n"
723 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
726 "Unable to save message: internal error.\r\n");
730 lprintf(CTDL_DEBUG, "Converting message...\n");
731 msg = convert_internet_message(body);
733 /* If the user is locally authenticated, FORCE the From: header to
734 * show up as the real sender. Yes, this violates the RFC standard,
735 * but IT MAKES SENSE. If you prefer strict RFC adherence over
736 * common sense, you can disable this in the configuration.
738 * We also set the "message room name" ('O' field) to MAILROOM
739 * (which is Mail> on most systems) to prevent it from getting set
740 * to something ugly like "0000058008.Sent Items>" when the message
741 * is read with a Citadel client.
743 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
744 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
745 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
746 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
747 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
748 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
749 msg->cm_fields['A'] = strdup(CC->user.fullname);
750 msg->cm_fields['N'] = strdup(config.c_nodename);
751 msg->cm_fields['H'] = strdup(config.c_humannode);
752 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
753 msg->cm_fields['O'] = strdup(MAILROOM);
756 /* Set the "envelope from" address */
757 if (msg->cm_fields['P'] != NULL) {
758 free(msg->cm_fields['P']);
760 msg->cm_fields['P'] = strdup(SMTP->from);
762 /* Set the "envelope to" address */
763 if (msg->cm_fields['V'] != NULL) {
764 free(msg->cm_fields['V']);
766 msg->cm_fields['V'] = strdup(SMTP->recipients);
768 /* Submit the message into the Citadel system. */
769 valid = validate_recipients(SMTP->recipients);
771 /* If there are modules that want to scan this message before final
772 * submission (such as virus checkers or spam filters), call them now
773 * and give them an opportunity to reject the message.
775 if (SMTP->is_unfiltered) {
779 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
782 if (scan_errors > 0) { /* We don't want this message! */
784 if (msg->cm_fields['0'] == NULL) {
785 msg->cm_fields['0'] = strdup(
786 "5.7.1 Message rejected by filter");
789 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
792 else { /* Ok, we'll accept this message. */
793 msgnum = CtdlSubmitMsg(msg, valid, "");
795 sprintf(result, "250 2.0.0 Message accepted.\r\n");
798 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
802 /* For SMTP and ESTMP, just print the result message. For LMTP, we
803 * have to print one result message for each recipient. Since there
804 * is nothing in Citadel which would cause different recipients to
805 * have different results, we can get away with just spitting out the
806 * same message once for each recipient.
809 for (i=0; i<SMTP->number_of_recipients; ++i) {
810 cprintf("%s", result);
814 cprintf("%s", result);
817 /* Write something to the syslog (which may or may not be where the
818 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
821 syslog((LOG_MAIL | LOG_INFO),
822 "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
825 SMTP->number_of_recipients,
833 CtdlFreeMessage(msg);
835 smtp_data_clear(); /* clear out the buffers now */
840 * implements the STARTTLS command (Citadel API version)
843 void smtp_starttls(void)
845 char ok_response[SIZ];
846 char nosup_response[SIZ];
847 char error_response[SIZ];
850 "200 2.0.0 Begin TLS negotiation now\r\n");
851 sprintf(nosup_response,
852 "554 5.7.3 TLS not supported here\r\n");
853 sprintf(error_response,
854 "554 5.7.3 Internal error\r\n");
855 CtdlStartTLS(ok_response, nosup_response, error_response);
863 * Main command loop for SMTP sessions.
865 void smtp_command_loop(void) {
869 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
870 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
871 lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
875 lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
876 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
878 if (SMTP->command_state == smtp_user) {
879 smtp_get_user(cmdbuf);
882 else if (SMTP->command_state == smtp_password) {
883 smtp_get_pass(cmdbuf);
886 else if (SMTP->command_state == smtp_plain) {
887 smtp_try_plain(cmdbuf);
890 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
891 smtp_auth(&cmdbuf[5]);
894 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
898 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
899 smtp_expn(&cmdbuf[5]);
902 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
903 smtp_hello(&cmdbuf[5], 0);
906 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
907 smtp_hello(&cmdbuf[5], 1);
910 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
911 smtp_hello(&cmdbuf[5], 2);
914 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
918 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
919 smtp_mail(&cmdbuf[5]);
922 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
923 cprintf("250 NOOP\r\n");
926 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
927 cprintf("221 Goodbye...\r\n");
932 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
933 smtp_rcpt(&cmdbuf[5]);
936 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
940 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
944 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
945 smtp_vrfy(&cmdbuf[5]);
949 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
958 /*****************************************************************************/
959 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
960 /*****************************************************************************/
967 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
970 void smtp_try(const char *key, const char *addr, int *status,
971 char *dsn, size_t n, long msgnum)
978 char user[1024], node[1024], name[1024];
991 /* Parse out the host portion of the recipient address */
992 process_rfc822_addr(addr, user, node, name);
994 lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
997 /* Load the message out of the database */
998 CC->redirect_buffer = malloc(SIZ);
999 CC->redirect_len = 0;
1000 CC->redirect_alloc = SIZ;
1001 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
1002 msgtext = CC->redirect_buffer;
1003 msg_size = CC->redirect_len;
1004 CC->redirect_buffer = NULL;
1005 CC->redirect_len = 0;
1006 CC->redirect_alloc = 0;
1008 /* Extract something to send later in the 'MAIL From:' command */
1009 strcpy(mailfrom, "");
1013 if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
1016 if (!strncasecmp(buf, "From:", 5)) {
1017 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
1019 for (i=0; i<strlen(mailfrom); ++i) {
1020 if (!isprint(mailfrom[i])) {
1021 strcpy(&mailfrom[i], &mailfrom[i+1]);
1026 /* Strip out parenthesized names */
1029 for (i=0; i<strlen(mailfrom); ++i) {
1030 if (mailfrom[i] == '(') lp = i;
1031 if (mailfrom[i] == ')') rp = i;
1033 if ((lp>0)&&(rp>lp)) {
1034 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
1037 /* Prefer brokketized names */
1040 for (i=0; i<strlen(mailfrom); ++i) {
1041 if (mailfrom[i] == '<') lp = i;
1042 if (mailfrom[i] == '>') rp = i;
1044 if ( (lp>=0) && (rp>lp) ) {
1046 strcpy(mailfrom, &mailfrom[lp]);
1051 } while (scan_done == 0);
1052 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
1053 stripallbut(mailfrom, '<', '>');
1055 /* Figure out what mail exchanger host we have to connect to */
1056 num_mxhosts = getmx(mxhosts, node);
1057 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
1058 if (num_mxhosts < 1) {
1060 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
1065 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
1066 extract_token(buf, mxhosts, mx, '|', sizeof buf);
1067 strcpy(mx_user, "");
1068 strcpy(mx_pass, "");
1069 if (num_tokens(buf, '@') > 1) {
1070 extract_token(mx_user, buf, 0, '@', sizeof mx_user);
1071 if (num_tokens(mx_user, ':') > 1) {
1072 extract_token(mx_pass, mx_user, 1, ':', sizeof mx_pass);
1073 remove_token(mx_user, 1, ':');
1075 remove_token(buf, 0, '@');
1077 extract_token(mx_host, buf, 0, ':', sizeof mx_host);
1078 extract_token(mx_port, buf, 1, ':', sizeof mx_port);
1080 strcpy(mx_port, "25");
1082 /* lprintf(CTDL_DEBUG, "FIXME user<%s> pass<%s> host<%s> port<%s>\n",
1083 mx_user, mx_pass, mx_host, mx_port); */
1084 lprintf(CTDL_DEBUG, "Trying %s : %s ...\n", mx_host, mx_port);
1085 sock = sock_connect(mx_host, mx_port, "tcp");
1086 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
1087 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
1090 snprintf(dsn, SIZ, "%s", strerror(errno));
1093 snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
1099 *status = 4; /* dsn is already filled in */
1103 /* Process the SMTP greeting from the server */
1104 if (ml_sock_gets(sock, buf) < 0) {
1106 strcpy(dsn, "Connection broken during SMTP conversation");
1109 lprintf(CTDL_DEBUG, "<%s\n", buf);
1110 if (buf[0] != '2') {
1111 if (buf[0] == '4') {
1113 safestrncpy(dsn, &buf[4], 1023);
1118 safestrncpy(dsn, &buf[4], 1023);
1123 /* At this point we know we are talking to a real SMTP server */
1125 /* Do a EHLO command. If it fails, try the HELO command. */
1126 snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
1127 lprintf(CTDL_DEBUG, ">%s", buf);
1128 sock_write(sock, buf, strlen(buf));
1129 if (ml_sock_gets(sock, buf) < 0) {
1131 strcpy(dsn, "Connection broken during SMTP HELO");
1134 lprintf(CTDL_DEBUG, "<%s\n", buf);
1135 if (buf[0] != '2') {
1136 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1137 lprintf(CTDL_DEBUG, ">%s", buf);
1138 sock_write(sock, buf, strlen(buf));
1139 if (ml_sock_gets(sock, buf) < 0) {
1141 strcpy(dsn, "Connection broken during SMTP HELO");
1145 if (buf[0] != '2') {
1146 if (buf[0] == '4') {
1148 safestrncpy(dsn, &buf[4], 1023);
1153 safestrncpy(dsn, &buf[4], 1023);
1158 /* Do an AUTH command if necessary */
1159 if (strlen(mx_user) > 0) {
1160 sprintf(buf, "%s%c%s%c%s%c", mx_user, 0, mx_user, 0, mx_pass, 0);
1161 CtdlEncodeBase64(mailfrom, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 3);
1162 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", mailfrom);
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 AUTH");
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);
1185 /* previous command succeeded, now try the MAIL From: command */
1186 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1187 lprintf(CTDL_DEBUG, ">%s", buf);
1188 sock_write(sock, buf, strlen(buf));
1189 if (ml_sock_gets(sock, buf) < 0) {
1191 strcpy(dsn, "Connection broken during SMTP MAIL");
1194 lprintf(CTDL_DEBUG, "<%s\n", buf);
1195 if (buf[0] != '2') {
1196 if (buf[0] == '4') {
1198 safestrncpy(dsn, &buf[4], 1023);
1203 safestrncpy(dsn, &buf[4], 1023);
1208 /* MAIL succeeded, now try the RCPT To: command */
1209 snprintf(buf, sizeof buf, "RCPT To: <%s@%s>\r\n", user, node);
1210 lprintf(CTDL_DEBUG, ">%s", buf);
1211 sock_write(sock, buf, strlen(buf));
1212 if (ml_sock_gets(sock, buf) < 0) {
1214 strcpy(dsn, "Connection broken during SMTP RCPT");
1217 lprintf(CTDL_DEBUG, "<%s\n", buf);
1218 if (buf[0] != '2') {
1219 if (buf[0] == '4') {
1221 safestrncpy(dsn, &buf[4], 1023);
1226 safestrncpy(dsn, &buf[4], 1023);
1231 /* RCPT succeeded, now try the DATA command */
1232 lprintf(CTDL_DEBUG, ">DATA\n");
1233 sock_write(sock, "DATA\r\n", 6);
1234 if (ml_sock_gets(sock, buf) < 0) {
1236 strcpy(dsn, "Connection broken during SMTP DATA");
1239 lprintf(CTDL_DEBUG, "<%s\n", buf);
1240 if (buf[0] != '3') {
1241 if (buf[0] == '4') {
1243 safestrncpy(dsn, &buf[4], 1023);
1248 safestrncpy(dsn, &buf[4], 1023);
1253 /* If we reach this point, the server is expecting data */
1254 sock_write(sock, msgtext, msg_size);
1255 if (msgtext[msg_size-1] != 10) {
1256 lprintf(CTDL_WARNING, "Possible problem: message did not "
1257 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1261 sock_write(sock, ".\r\n", 3);
1262 if (ml_sock_gets(sock, buf) < 0) {
1264 strcpy(dsn, "Connection broken during SMTP message transmit");
1267 lprintf(CTDL_DEBUG, "%s\n", buf);
1268 if (buf[0] != '2') {
1269 if (buf[0] == '4') {
1271 safestrncpy(dsn, &buf[4], 1023);
1276 safestrncpy(dsn, &buf[4], 1023);
1282 safestrncpy(dsn, &buf[4], 1023);
1285 lprintf(CTDL_DEBUG, ">QUIT\n");
1286 sock_write(sock, "QUIT\r\n", 6);
1287 ml_sock_gets(sock, buf);
1288 lprintf(CTDL_DEBUG, "<%s\n", buf);
1289 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1292 bail: free(msgtext);
1295 /* Write something to the syslog (which may or may not be where the
1296 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1298 if (enable_syslog) {
1299 syslog((LOG_MAIL | LOG_INFO),
1300 "%ld: to=<%s>, relay=%s, stat=%s",
1314 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1315 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1316 * a "bounce" message (delivery status notification).
1318 void smtp_do_bounce(char *instr) {
1326 char bounceto[1024];
1327 int num_bounces = 0;
1328 int bounce_this = 0;
1329 long bounce_msgid = (-1);
1330 time_t submitted = 0L;
1331 struct CtdlMessage *bmsg = NULL;
1333 struct recptypes *valid;
1334 int successful_bounce = 0;
1336 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1337 strcpy(bounceto, "");
1339 lines = num_tokens(instr, '\n');
1342 /* See if it's time to give up on delivery of this message */
1343 for (i=0; i<lines; ++i) {
1344 extract_token(buf, instr, i, '\n', sizeof buf);
1345 extract_token(key, buf, 0, '|', sizeof key);
1346 extract_token(addr, buf, 1, '|', sizeof addr);
1347 if (!strcasecmp(key, "submitted")) {
1348 submitted = atol(addr);
1352 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1356 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1357 if (bmsg == NULL) return;
1358 memset(bmsg, 0, sizeof(struct CtdlMessage));
1360 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1361 bmsg->cm_anon_type = MES_NORMAL;
1362 bmsg->cm_format_type = 1;
1363 bmsg->cm_fields['A'] = strdup("Citadel");
1364 bmsg->cm_fields['O'] = strdup(MAILROOM);
1365 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1366 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1368 if (give_up) bmsg->cm_fields['M'] = strdup(
1369 "A message you sent could not be delivered to some or all of its recipients\n"
1370 "due to prolonged unavailability of its destination(s).\n"
1371 "Giving up on the following addresses:\n\n"
1374 else bmsg->cm_fields['M'] = strdup(
1375 "A message you sent could not be delivered to some or all of its recipients.\n"
1376 "The following addresses were undeliverable:\n\n"
1380 * Now go through the instructions checking for stuff.
1382 for (i=0; i<lines; ++i) {
1383 extract_token(buf, instr, i, '\n', sizeof buf);
1384 extract_token(key, buf, 0, '|', sizeof key);
1385 extract_token(addr, buf, 1, '|', sizeof addr);
1386 status = extract_int(buf, 2);
1387 extract_token(dsn, buf, 3, '|', sizeof dsn);
1390 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1391 key, addr, status, dsn);
1393 if (!strcasecmp(key, "bounceto")) {
1394 strcpy(bounceto, addr);
1398 (!strcasecmp(key, "local"))
1399 || (!strcasecmp(key, "remote"))
1400 || (!strcasecmp(key, "ignet"))
1401 || (!strcasecmp(key, "room"))
1403 if (status == 5) bounce_this = 1;
1404 if (give_up) bounce_this = 1;
1410 if (bmsg->cm_fields['M'] == NULL) {
1411 lprintf(CTDL_ERR, "ERROR ... M field is null "
1412 "(%s:%d)\n", __FILE__, __LINE__);
1415 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1416 strlen(bmsg->cm_fields['M']) + 1024 );
1417 strcat(bmsg->cm_fields['M'], addr);
1418 strcat(bmsg->cm_fields['M'], ": ");
1419 strcat(bmsg->cm_fields['M'], dsn);
1420 strcat(bmsg->cm_fields['M'], "\n");
1422 remove_token(instr, i, '\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 (strlen(bounceto) == 0) {
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) {
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);
1492 (!strcasecmp(key, "local"))
1493 || (!strcasecmp(key, "remote"))
1494 || (!strcasecmp(key, "ignet"))
1495 || (!strcasecmp(key, "room"))
1497 if (status == 2) completed = 1;
1502 remove_token(instr, i, '\n');
1515 * Called by smtp_do_queue() to handle an individual message.
1517 void smtp_do_procmsg(long msgnum, void *userdata) {
1518 struct CtdlMessage *msg;
1520 char *results = NULL;
1528 long text_msgid = (-1);
1529 int incomplete_deliveries_remaining;
1530 time_t attempted = 0L;
1531 time_t last_attempted = 0L;
1532 time_t retry = SMTP_RETRY_INTERVAL;
1534 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1536 msg = CtdlFetchMessage(msgnum, 1);
1538 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1542 instr = strdup(msg->cm_fields['M']);
1543 CtdlFreeMessage(msg);
1545 /* Strip out the headers amd any other non-instruction line */
1546 lines = num_tokens(instr, '\n');
1547 for (i=0; i<lines; ++i) {
1548 extract_token(buf, instr, i, '\n', sizeof buf);
1549 if (num_tokens(buf, '|') < 2) {
1550 remove_token(instr, i, '\n');
1556 /* Learn the message ID and find out about recent delivery attempts */
1557 lines = num_tokens(instr, '\n');
1558 for (i=0; i<lines; ++i) {
1559 extract_token(buf, instr, i, '\n', sizeof buf);
1560 extract_token(key, buf, 0, '|', sizeof key);
1561 if (!strcasecmp(key, "msgid")) {
1562 text_msgid = extract_long(buf, 1);
1564 if (!strcasecmp(key, "retry")) {
1565 /* double the retry interval after each attempt */
1566 retry = extract_long(buf, 1) * 2L;
1567 if (retry > SMTP_RETRY_MAX) {
1568 retry = SMTP_RETRY_MAX;
1570 remove_token(instr, i, '\n');
1572 if (!strcasecmp(key, "attempted")) {
1573 attempted = extract_long(buf, 1);
1574 if (attempted > last_attempted)
1575 last_attempted = attempted;
1580 * Postpone delivery if we've already tried recently.
1582 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1583 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1590 * Bail out if there's no actual message associated with this
1592 if (text_msgid < 0L) {
1593 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1598 /* Plow through the instructions looking for 'remote' directives and
1599 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1600 * were experienced and it's time to try again)
1602 lines = num_tokens(instr, '\n');
1603 for (i=0; i<lines; ++i) {
1604 extract_token(buf, instr, i, '\n', sizeof buf);
1605 extract_token(key, buf, 0, '|', sizeof key);
1606 extract_token(addr, buf, 1, '|', sizeof addr);
1607 status = extract_int(buf, 2);
1608 extract_token(dsn, buf, 3, '|', sizeof dsn);
1609 if ( (!strcasecmp(key, "remote"))
1610 && ((status==0)||(status==3)||(status==4)) ) {
1612 /* Remove this "remote" instruction from the set,
1613 * but replace the set's final newline if
1614 * remove_token() stripped it. It has to be there.
1616 remove_token(instr, i, '\n');
1617 if (instr[strlen(instr)-1] != '\n') {
1618 strcat(instr, "\n");
1623 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1624 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1626 if (results == NULL) {
1627 results = malloc(1024);
1628 memset(results, 0, 1024);
1631 results = realloc(results,
1632 strlen(results) + 1024);
1634 snprintf(&results[strlen(results)], 1024,
1636 key, addr, status, dsn);
1641 if (results != NULL) {
1642 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1643 strcat(instr, results);
1648 /* Generate 'bounce' messages */
1649 smtp_do_bounce(instr);
1651 /* Go through the delivery list, deleting completed deliveries */
1652 incomplete_deliveries_remaining =
1653 smtp_purge_completed_deliveries(instr);
1657 * No delivery instructions remain, so delete both the instructions
1658 * message and the message message.
1660 if (incomplete_deliveries_remaining <= 0) {
1662 delmsgs[0] = msgnum;
1663 delmsgs[1] = text_msgid;
1664 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "", 0);
1668 * Uncompleted delivery instructions remain, so delete the old
1669 * instructions and replace with the updated ones.
1671 if (incomplete_deliveries_remaining > 0) {
1672 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "", 0);
1673 msg = malloc(sizeof(struct CtdlMessage));
1674 memset(msg, 0, sizeof(struct CtdlMessage));
1675 msg->cm_magic = CTDLMESSAGE_MAGIC;
1676 msg->cm_anon_type = MES_NORMAL;
1677 msg->cm_format_type = FMT_RFC822;
1678 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1679 snprintf(msg->cm_fields['M'],
1681 "Content-type: %s\n\n%s\n"
1684 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1685 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1686 CtdlFreeMessage(msg);
1697 * Run through the queue sending out messages.
1699 void smtp_do_queue(void) {
1700 static int doing_queue = 0;
1703 * This is a simple concurrency check to make sure only one queue run
1704 * is done at a time. We could do this with a mutex, but since we
1705 * don't really require extremely fine granularity here, we'll do it
1706 * with a static variable instead.
1708 if (doing_queue) return;
1712 * Go ahead and run the queue
1714 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1716 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1717 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1720 CtdlForEachMessage(MSGS_ALL, 0L, NULL,
1721 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1723 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1730 /*****************************************************************************/
1731 /* SMTP UTILITY COMMANDS */
1732 /*****************************************************************************/
1734 void cmd_smtp(char *argbuf) {
1741 if (CtdlAccessCheck(ac_aide)) return;
1743 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1745 if (!strcasecmp(cmd, "mx")) {
1746 extract_token(node, argbuf, 1, '|', sizeof node);
1747 num_mxhosts = getmx(buf, node);
1748 cprintf("%d %d MX hosts listed for %s\n",
1749 LISTING_FOLLOWS, num_mxhosts, node);
1750 for (i=0; i<num_mxhosts; ++i) {
1751 extract_token(node, buf, i, '|', sizeof node);
1752 cprintf("%s\n", node);
1758 else if (!strcasecmp(cmd, "runqueue")) {
1760 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1765 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1772 * Initialize the SMTP outbound queue
1774 void smtp_init_spoolout(void) {
1775 struct ctdlroom qrbuf;
1778 * Create the room. This will silently fail if the room already
1779 * exists, and that's perfectly ok, because we want it to exist.
1781 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1784 * Make sure it's set to be a "system room" so it doesn't show up
1785 * in the <K>nown rooms list for Aides.
1787 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1788 qrbuf.QRflags2 |= QR2_SYSTEM;
1796 /*****************************************************************************/
1797 /* MODULE INITIALIZATION STUFF */
1798 /*****************************************************************************/
1800 * This cleanup function blows away the temporary memory used by
1803 void smtp_cleanup_function(void) {
1805 /* Don't do this stuff if this is not an SMTP session! */
1806 if (CC->h_command_function != smtp_command_loop) return;
1808 lprintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1818 char *serv_smtp_init(void)
1821 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1828 CtdlRegisterServiceHook(config.c_smtps_port,
1835 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1841 CtdlRegisterServiceHook(0, /* local LMTP */
1847 CtdlRegisterServiceHook(0, /* local LMTP */
1848 file_lmtp_unfiltered_socket,
1849 lmtp_unfiltered_greeting,
1853 smtp_init_spoolout();
1854 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1855 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1856 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");