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");
1088 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
1092 *status = 4; /* dsn is already filled in */
1096 /* Process the SMTP greeting from the server */
1097 if (ml_sock_gets(sock, buf) < 0) {
1099 strcpy(dsn, "Connection broken during SMTP conversation");
1102 lprintf(CTDL_DEBUG, "<%s\n", buf);
1103 if (buf[0] != '2') {
1104 if (buf[0] == '4') {
1106 safestrncpy(dsn, &buf[4], 1023);
1111 safestrncpy(dsn, &buf[4], 1023);
1116 /* At this point we know we are talking to a real SMTP server */
1118 /* Do a EHLO command. If it fails, try the HELO command. */
1119 snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
1120 lprintf(CTDL_DEBUG, ">%s", buf);
1121 sock_write(sock, buf, strlen(buf));
1122 if (ml_sock_gets(sock, buf) < 0) {
1124 strcpy(dsn, "Connection broken during SMTP HELO");
1127 lprintf(CTDL_DEBUG, "<%s\n", buf);
1128 if (buf[0] != '2') {
1129 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1130 lprintf(CTDL_DEBUG, ">%s", buf);
1131 sock_write(sock, buf, strlen(buf));
1132 if (ml_sock_gets(sock, buf) < 0) {
1134 strcpy(dsn, "Connection broken during SMTP HELO");
1138 if (buf[0] != '2') {
1139 if (buf[0] == '4') {
1141 safestrncpy(dsn, &buf[4], 1023);
1146 safestrncpy(dsn, &buf[4], 1023);
1151 /* Do an AUTH command if necessary */
1152 if (strlen(mx_user) > 0) {
1153 sprintf(buf, "%s%c%s%c%s%c", mx_user, 0, mx_user, 0, mx_pass, 0);
1154 CtdlEncodeBase64(mailfrom, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 3);
1155 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", mailfrom);
1156 lprintf(CTDL_DEBUG, ">%s", buf);
1157 sock_write(sock, buf, strlen(buf));
1158 if (ml_sock_gets(sock, buf) < 0) {
1160 strcpy(dsn, "Connection broken during SMTP AUTH");
1163 lprintf(CTDL_DEBUG, "<%s\n", buf);
1164 if (buf[0] != '2') {
1165 if (buf[0] == '4') {
1167 safestrncpy(dsn, &buf[4], 1023);
1172 safestrncpy(dsn, &buf[4], 1023);
1178 /* previous command succeeded, now try the MAIL From: command */
1179 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1180 lprintf(CTDL_DEBUG, ">%s", buf);
1181 sock_write(sock, buf, strlen(buf));
1182 if (ml_sock_gets(sock, buf) < 0) {
1184 strcpy(dsn, "Connection broken during SMTP MAIL");
1187 lprintf(CTDL_DEBUG, "<%s\n", buf);
1188 if (buf[0] != '2') {
1189 if (buf[0] == '4') {
1191 safestrncpy(dsn, &buf[4], 1023);
1196 safestrncpy(dsn, &buf[4], 1023);
1201 /* MAIL succeeded, now try the RCPT To: command */
1202 snprintf(buf, sizeof buf, "RCPT To: <%s@%s>\r\n", user, node);
1203 lprintf(CTDL_DEBUG, ">%s", buf);
1204 sock_write(sock, buf, strlen(buf));
1205 if (ml_sock_gets(sock, buf) < 0) {
1207 strcpy(dsn, "Connection broken during SMTP RCPT");
1210 lprintf(CTDL_DEBUG, "<%s\n", buf);
1211 if (buf[0] != '2') {
1212 if (buf[0] == '4') {
1214 safestrncpy(dsn, &buf[4], 1023);
1219 safestrncpy(dsn, &buf[4], 1023);
1224 /* RCPT succeeded, now try the DATA command */
1225 lprintf(CTDL_DEBUG, ">DATA\n");
1226 sock_write(sock, "DATA\r\n", 6);
1227 if (ml_sock_gets(sock, buf) < 0) {
1229 strcpy(dsn, "Connection broken during SMTP DATA");
1232 lprintf(CTDL_DEBUG, "<%s\n", buf);
1233 if (buf[0] != '3') {
1234 if (buf[0] == '4') {
1236 safestrncpy(dsn, &buf[4], 1023);
1241 safestrncpy(dsn, &buf[4], 1023);
1246 /* If we reach this point, the server is expecting data */
1247 sock_write(sock, msgtext, msg_size);
1248 if (msgtext[msg_size-1] != 10) {
1249 lprintf(CTDL_WARNING, "Possible problem: message did not "
1250 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1254 sock_write(sock, ".\r\n", 3);
1255 if (ml_sock_gets(sock, buf) < 0) {
1257 strcpy(dsn, "Connection broken during SMTP message transmit");
1260 lprintf(CTDL_DEBUG, "%s\n", buf);
1261 if (buf[0] != '2') {
1262 if (buf[0] == '4') {
1264 safestrncpy(dsn, &buf[4], 1023);
1269 safestrncpy(dsn, &buf[4], 1023);
1275 safestrncpy(dsn, &buf[4], 1023);
1278 lprintf(CTDL_DEBUG, ">QUIT\n");
1279 sock_write(sock, "QUIT\r\n", 6);
1280 ml_sock_gets(sock, buf);
1281 lprintf(CTDL_DEBUG, "<%s\n", buf);
1282 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1285 bail: free(msgtext);
1288 /* Write something to the syslog (which may or may not be where the
1289 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1291 if (enable_syslog) {
1292 syslog((LOG_MAIL | LOG_INFO),
1293 "%ld: to=<%s>, relay=%s, stat=%s",
1307 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1308 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1309 * a "bounce" message (delivery status notification).
1311 void smtp_do_bounce(char *instr) {
1319 char bounceto[1024];
1320 int num_bounces = 0;
1321 int bounce_this = 0;
1322 long bounce_msgid = (-1);
1323 time_t submitted = 0L;
1324 struct CtdlMessage *bmsg = NULL;
1326 struct recptypes *valid;
1327 int successful_bounce = 0;
1329 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1330 strcpy(bounceto, "");
1332 lines = num_tokens(instr, '\n');
1335 /* See if it's time to give up on delivery of this message */
1336 for (i=0; i<lines; ++i) {
1337 extract_token(buf, instr, i, '\n', sizeof buf);
1338 extract_token(key, buf, 0, '|', sizeof key);
1339 extract_token(addr, buf, 1, '|', sizeof addr);
1340 if (!strcasecmp(key, "submitted")) {
1341 submitted = atol(addr);
1345 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1349 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1350 if (bmsg == NULL) return;
1351 memset(bmsg, 0, sizeof(struct CtdlMessage));
1353 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1354 bmsg->cm_anon_type = MES_NORMAL;
1355 bmsg->cm_format_type = 1;
1356 bmsg->cm_fields['A'] = strdup("Citadel");
1357 bmsg->cm_fields['O'] = strdup(MAILROOM);
1358 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1359 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1361 if (give_up) bmsg->cm_fields['M'] = strdup(
1362 "A message you sent could not be delivered to some or all of its recipients\n"
1363 "due to prolonged unavailability of its destination(s).\n"
1364 "Giving up on the following addresses:\n\n"
1367 else bmsg->cm_fields['M'] = strdup(
1368 "A message you sent could not be delivered to some or all of its recipients.\n"
1369 "The following addresses were undeliverable:\n\n"
1373 * Now go through the instructions checking for stuff.
1375 for (i=0; i<lines; ++i) {
1376 extract_token(buf, instr, i, '\n', sizeof buf);
1377 extract_token(key, buf, 0, '|', sizeof key);
1378 extract_token(addr, buf, 1, '|', sizeof addr);
1379 status = extract_int(buf, 2);
1380 extract_token(dsn, buf, 3, '|', sizeof dsn);
1383 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1384 key, addr, status, dsn);
1386 if (!strcasecmp(key, "bounceto")) {
1387 strcpy(bounceto, addr);
1391 (!strcasecmp(key, "local"))
1392 || (!strcasecmp(key, "remote"))
1393 || (!strcasecmp(key, "ignet"))
1394 || (!strcasecmp(key, "room"))
1396 if (status == 5) bounce_this = 1;
1397 if (give_up) bounce_this = 1;
1403 if (bmsg->cm_fields['M'] == NULL) {
1404 lprintf(CTDL_ERR, "ERROR ... M field is null "
1405 "(%s:%d)\n", __FILE__, __LINE__);
1408 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1409 strlen(bmsg->cm_fields['M']) + 1024 );
1410 strcat(bmsg->cm_fields['M'], addr);
1411 strcat(bmsg->cm_fields['M'], ": ");
1412 strcat(bmsg->cm_fields['M'], dsn);
1413 strcat(bmsg->cm_fields['M'], "\n");
1415 remove_token(instr, i, '\n');
1421 /* Deliver the bounce if there's anything worth mentioning */
1422 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1423 if (num_bounces > 0) {
1425 /* First try the user who sent the message */
1426 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1427 if (strlen(bounceto) == 0) {
1428 lprintf(CTDL_ERR, "No bounce address specified\n");
1429 bounce_msgid = (-1L);
1432 /* Can we deliver the bounce to the original sender? */
1433 valid = validate_recipients(bounceto);
1434 if (valid != NULL) {
1435 if (valid->num_error == 0) {
1436 CtdlSubmitMsg(bmsg, valid, "");
1437 successful_bounce = 1;
1441 /* If not, post it in the Aide> room */
1442 if (successful_bounce == 0) {
1443 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1446 /* Free up the memory we used */
1447 if (valid != NULL) {
1452 CtdlFreeMessage(bmsg);
1453 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1458 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1459 * set of delivery instructions for completed deliveries and remove them.
1461 * It returns the number of incomplete deliveries remaining.
1463 int smtp_purge_completed_deliveries(char *instr) {
1474 lines = num_tokens(instr, '\n');
1475 for (i=0; i<lines; ++i) {
1476 extract_token(buf, instr, i, '\n', sizeof buf);
1477 extract_token(key, buf, 0, '|', sizeof key);
1478 extract_token(addr, buf, 1, '|', sizeof addr);
1479 status = extract_int(buf, 2);
1480 extract_token(dsn, buf, 3, '|', sizeof dsn);
1485 (!strcasecmp(key, "local"))
1486 || (!strcasecmp(key, "remote"))
1487 || (!strcasecmp(key, "ignet"))
1488 || (!strcasecmp(key, "room"))
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;
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, "", 0);
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, "", 0);
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");
1811 char *serv_smtp_init(void)
1814 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1821 CtdlRegisterServiceHook(config.c_smtps_port,
1828 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1834 CtdlRegisterServiceHook(0, /* local LMTP */
1840 CtdlRegisterServiceHook(0, /* local LMTP */
1841 file_lmtp_unfiltered_socket,
1842 lmtp_unfiltered_greeting,
1846 smtp_init_spoolout();
1847 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1848 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1849 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");