1 // This module is an SMTP and ESMTP server for the Citadel system.
2 // It is compliant with all of the following:
4 // RFC 821 - Simple Mail Transfer Protocol
5 // RFC 876 - Survey of SMTP Implementations
6 // RFC 1047 - Duplicate messages and SMTP
7 // RFC 1652 - 8 bit MIME
8 // RFC 1869 - Extended Simple Mail Transfer Protocol
9 // RFC 1870 - SMTP Service Extension for Message Size Declaration
10 // RFC 2033 - Local Mail Transfer Protocol
11 // RFC 2197 - SMTP Service Extension for Command Pipelining
12 // RFC 2476 - Message Submission
13 // RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
14 // RFC 2554 - SMTP Service Extension for Authentication
15 // RFC 2821 - Simple Mail Transfer Protocol
16 // RFC 2822 - Internet Message Format
17 // RFC 2920 - SMTP Service Extension for Command Pipelining
19 // The VRFY and EXPN commands have been removed from this implementation
20 // because nobody uses these commands anymore, except for spammers.
22 // Copyright (c) 1998-2023 by the citadel.org team
24 // This program is open source software; you can redistribute it and/or modify
25 // it under the terms of the GNU General Public License version 3.
27 // This program is distributed in the hope that it will be useful,
28 // but WITHOUT ANY WARRANTY; without even the implied warranty of
29 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 // GNU General Public License for more details.
32 #include "../../sysdep.h"
41 #include <sys/types.h>
48 #include <sys/socket.h>
49 #include <netinet/in.h>
50 #include <arpa/inet.h>
52 #include <libcitadel.h>
53 #include "../../citadel_defs.h"
54 #include "../../server.h"
55 #include "../../citserver.h"
56 #include "../../support.h"
57 #include "../../config.h"
58 #include "../../control.h"
59 #include "../../user_ops.h"
60 #include "../../room_ops.h"
61 #include "../../database.h"
62 #include "../../msgbase.h"
63 #include "../../internet_addressing.h"
64 #include "../../genstamp.h"
65 #include "../../domain.h"
66 #include "../../clientsocket.h"
67 #include "../../locate_host.h"
68 #include "../../citadel_dirs.h"
69 #include "../../ctdl_module.h"
71 #include "smtp_util.h"
73 enum { // Command states for login authentication
87 // Here's where our SMTP session begins its happy day.
88 void smtp_greeting(int is_msa) {
89 char message_to_spammer[1024];
91 strcpy(CC->cs_clientname, "SMTP session");
93 CC->cs_flags |= CS_STEALTH;
94 CC->session_specific_data = malloc(sizeof(struct citsmtp));
95 memset(SMTP, 0, sizeof(struct citsmtp));
96 SMTP->is_msa = is_msa;
97 SMTP->Cmd = NewStrBufPlain(NULL, SIZ);
98 SMTP->helo_node = NewStrBuf();
99 SMTP->from = NewStrBufPlain(NULL, SIZ);
100 SMTP->recipients = NewStrBufPlain(NULL, SIZ);
101 SMTP->OneRcpt = NewStrBufPlain(NULL, SIZ);
102 SMTP->preferred_sender_email = NULL;
103 SMTP->preferred_sender_name = NULL;
105 // If this config option is set, reject connections from problem
106 // addresses immediately instead of after they execute a RCPT
107 if ( (CtdlGetConfigInt("c_rbl_at_greeting")) && (SMTP->is_msa == 0) ) {
108 if (rbl_check(CC->cs_addr, message_to_spammer)) {
109 if (server_shutting_down)
110 cprintf("421 %s\r\n", message_to_spammer);
112 cprintf("550 %s\r\n", message_to_spammer);
113 CC->kill_me = KILLME_SPAMMER;
114 // no need to free_recipients(valid), it's not allocated yet
119 // Otherwise we're either clean or we check later.
121 if (CC->nologin==1) {
122 cprintf("451 Too many connections are already open; please try again later.\r\n");
123 CC->kill_me = KILLME_MAX_SESSIONS_EXCEEDED;
124 // no need to free_recipients(valid), it's not allocated yet
128 // Note: the FQDN *must* appear as the first thing after the 220 code.
129 // Some clients (including citmail.c) depend on it being there.
130 cprintf("220 %s ESMTP Citadel server ready.\r\n", CtdlGetConfigStr("c_fqdn"));
134 // SMTPS is just like SMTP, except it goes crypto right away.
135 void smtps_greeting(void) {
136 CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
138 if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO; // kill session if no crypto
144 // SMTP MSA port requires authentication.
145 void smtp_msa_greeting(void) {
150 // LMTP is like SMTP but with some extra bonus footage added.
151 void lmtp_greeting(void) {
158 // Generic SMTP MTA greeting
159 void smtp_mta_greeting(void) {
164 // We also have an unfiltered LMTP socket that bypasses spam filters.
165 void lmtp_unfiltered_greeting(void) {
168 SMTP->is_unfiltered = 1;
172 // Login greeting common to all auth methods
173 void smtp_auth_greeting(void) {
174 cprintf("235 Hello, %s\r\n", CC->user.fullname);
175 syslog(LOG_INFO, "serv_smtp: SMTP authenticated %s", CC->user.fullname);
176 CC->internal_pgm = 0;
177 CC->cs_flags &= ~CS_STEALTH;
181 // Implement HELO and EHLO commands.
182 // which_command: 0=HELO, 1=EHLO, 2=LHLO
183 void smtp_hello(int which_command) {
185 if (StrLength(SMTP->Cmd) >= 6) {
186 FlushStrBuf(SMTP->helo_node);
187 StrBufAppendBuf(SMTP->helo_node, SMTP->Cmd, 5);
190 if ( (which_command != LHLO) && (SMTP->is_lmtp) ) {
191 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
195 if ( (which_command == LHLO) && (SMTP->is_lmtp == 0) ) {
196 cprintf("500 LHLO is only allowed when running LMTP\r\n");
200 if (which_command == HELO) {
201 cprintf("250 Hello %s (%s [%s])\r\n",
202 ChrPtr(SMTP->helo_node),
208 if (which_command == EHLO) {
209 cprintf("250-Hello %s (%s [%s])\r\n", ChrPtr(SMTP->helo_node), CC->cs_host, CC->cs_addr);
212 cprintf("250-Greetings and joyous salutations.\r\n");
214 cprintf("250-HELP\r\n");
215 cprintf("250-SIZE %ld\r\n", CtdlGetConfigLong("c_maxmsglen"));
218 // Offer TLS, but only if TLS is not already active.
219 // Furthermore, only offer TLS when running on the SMTP-MSA port, not on the SMTP-MTA port,
220 // because if our server doesn't have a trusted certificate, some mailers will refuse to talk to it.
221 if ( (!CC->redirect_ssl) && (SMTP->is_msa) ) {
222 cprintf("250-STARTTLS\r\n");
226 cprintf("250-AUTH LOGIN PLAIN\r\n"
227 "250-AUTH=LOGIN PLAIN\r\n"
234 // Backend function for smtp_webcit_preferences_hack().
235 // Look at a message and determine if it's the preferences file.
236 void smtp_webcit_preferences_hack_backend(long msgnum, void *userdata) {
237 struct CtdlMessage *msg;
238 char **webcit_conf = (char **) userdata;
241 return; // already got it
244 msg = CtdlFetchMessage(msgnum, 1);
249 if ( !CM_IsEmpty(msg, eMsgSubject) && (!strcasecmp(msg->cm_fields[eMsgSubject], "__ WebCit Preferences __"))) {
250 // This is it! Change ownership of the message text so it doesn't get freed.
251 *webcit_conf = (char *)msg->cm_fields[eMessageText];
252 msg->cm_fields[eMessageText] = NULL;
258 // The configuration item for the user's preferred display name for outgoing email is, unfortunately,
259 // stored in the account's WebCit configuration. We have to fetch it now.
260 void smtp_webcit_preferences_hack(void) {
261 char config_roomname[ROOMNAMELEN];
262 char *webcit_conf = NULL;
264 snprintf(config_roomname, sizeof config_roomname, "%010ld.%s", CC->user.usernum, USERCONFIGROOM);
265 if (CtdlGetRoom(&CC->room, config_roomname) != 0) {
269 // Find the WebCit configuration message
270 CtdlForEachMessage(MSGS_ALL, 1, NULL, NULL, NULL, smtp_webcit_preferences_hack_backend, (void *)&webcit_conf);
276 // Parse the webcit configuration and attempt to do something useful with it
277 char *str = webcit_conf;
279 char *this_line = NULL;
280 while (this_line = strtok_r(str, "\n", &saveptr), this_line != NULL) {
282 if (!strncasecmp(this_line, "defaultfrom|", 12)) {
283 SMTP->preferred_sender_email = NewStrBufPlain(&this_line[12], -1);
285 if (!strncasecmp(this_line, "defaultname|", 12)) {
286 SMTP->preferred_sender_name = NewStrBufPlain(&this_line[12], -1);
288 if ((!strncasecmp(this_line, "defaultname|", 12)) && (SMTP->preferred_sender_name == NULL)) {
289 SMTP->preferred_sender_name = NewStrBufPlain(&this_line[12], -1);
297 // Implement HELP command.
298 void smtp_help(void) {
299 cprintf("214 RTFM http://www.ietf.org/rfc/rfc2821.txt\r\n");
303 void smtp_get_user(int offset) {
306 StrBuf *UserName = NewStrBufDup(SMTP->Cmd);
307 StrBufCutLeft(UserName, offset);
308 StrBufDecodeBase64(UserName);
310 if (CtdlLoginExistingUser(ChrPtr(UserName)) == login_ok) {
311 size_t len = CtdlEncodeBase64(buf, "Password:", 9, BASE64_NO_LINEBREAKS);
313 if (buf[len - 1] == '\n') {
316 cprintf("334 %s\r\n", buf);
317 SMTP->command_state = smtp_password;
320 cprintf("500 No such user.\r\n");
321 SMTP->command_state = smtp_command;
323 FreeStrBuf(&UserName);
327 void smtp_get_pass(void) {
330 memset(password, 0, sizeof(password));
331 StrBufDecodeBase64(SMTP->Cmd);
332 syslog(LOG_DEBUG, "serv_smtp: trying <%s>", password);
333 if (CtdlTryPassword(SKEY(SMTP->Cmd)) == pass_ok) {
334 smtp_auth_greeting();
337 cprintf("535 Authentication failed.\r\n");
339 SMTP->command_state = smtp_command;
343 // Back end for PLAIN auth method (either inline or multistate)
344 void smtp_try_plain(void) {
345 const char*decoded_authstring;
346 char ident[256] = "";
355 memset(pass, 0, sizeof(pass));
356 decoded_len = StrBufDecodeBase64(SMTP->Cmd);
358 if (decoded_len > 0) {
359 decoded_authstring = ChrPtr(SMTP->Cmd);
361 len = safestrncpy(ident, decoded_authstring, sizeof ident);
363 decoded_len -= len - 1;
364 decoded_authstring += len + 1;
366 if (decoded_len > 0) {
367 len = safestrncpy(user, decoded_authstring, sizeof user);
369 decoded_authstring += len + 1;
370 decoded_len -= len - 1;
373 if (decoded_len > 0) {
374 plen = safestrncpy(pass, decoded_authstring, sizeof pass);
377 plen = sizeof(pass) - 1;
381 SMTP->command_state = smtp_command;
383 if (!IsEmptyStr(ident)) {
384 result = CtdlLoginExistingUser(ident);
387 result = CtdlLoginExistingUser(user);
390 if (result == login_ok) {
391 if (CtdlTryPassword(pass, plen) == pass_ok) {
392 smtp_webcit_preferences_hack();
393 smtp_auth_greeting();
397 cprintf("504 Authentication failed.\r\n");
401 // Attempt to perform authenticated SMTP
402 void smtp_auth(void) {
403 char username_prompt[64];
405 char encoded_authstring[1024];
408 cprintf("504 Already logged in.\r\n");
412 if (StrLength(SMTP->Cmd) < 6) {
413 cprintf("501 Syntax error\r\n");
417 extract_token(method, ChrPtr(SMTP->Cmd) + 5, 0, ' ', sizeof method);
419 if (!strncasecmp(method, "login", 5) ) {
420 if (StrLength(SMTP->Cmd) >= 12) {
421 syslog(LOG_DEBUG, "serv_smtp: username <%s> supplied inline", ChrPtr(SMTP->Cmd)+11);
425 size_t len = CtdlEncodeBase64(username_prompt, "Username:", 9, BASE64_NO_LINEBREAKS);
426 if (username_prompt[len - 1] == '\n') {
427 username_prompt[len - 1] = '\0';
429 cprintf("334 %s\r\n", username_prompt);
430 SMTP->command_state = smtp_user;
435 if (!strncasecmp(method, "plain", 5) ) {
437 if (num_tokens(ChrPtr(SMTP->Cmd) + 5, ' ') < 2) {
439 SMTP->command_state = smtp_plain;
443 len = extract_token(encoded_authstring, ChrPtr(SMTP->Cmd) + 5, 1, ' ', sizeof encoded_authstring);
444 StrBufPlain(SMTP->Cmd, encoded_authstring, len);
449 cprintf("504 Unknown authentication method.\r\n");
454 // Implements the RSET (reset state) command.
455 // Currently this just zeroes out the state buffer. If pointers to data
456 // allocated with malloc() are ever placed in the state buffer, we have to
457 // be sure to free() them first!
459 // Set do_response to nonzero to output the SMTP RSET response code.
460 void smtp_rset(int do_response) {
461 FlushStrBuf(SMTP->Cmd);
462 FlushStrBuf(SMTP->helo_node);
463 FlushStrBuf(SMTP->from);
464 FlushStrBuf(SMTP->recipients);
465 FlushStrBuf(SMTP->OneRcpt);
467 SMTP->command_state = 0;
468 SMTP->number_of_recipients = 0;
469 SMTP->delivery_mode = 0;
470 SMTP->message_originated_locally = 0;
472 // is_lmtp and is_unfiltered should not be cleared.
475 cprintf("250 Zap!\r\n");
480 // Clear out the portions of the state buffer that need to be cleared out
481 // after the DATA command finishes.
482 void smtp_data_clear(void) {
483 FlushStrBuf(SMTP->from);
484 FlushStrBuf(SMTP->recipients);
485 FlushStrBuf(SMTP->OneRcpt);
486 SMTP->number_of_recipients = 0;
487 SMTP->delivery_mode = 0;
488 SMTP->message_originated_locally = 0;
492 // Implements the "MAIL FROM:" command
493 void smtp_mail(void) {
498 if (StrLength(SMTP->from) > 0) {
499 cprintf("503 Only one sender permitted\r\n");
503 if (StrLength(SMTP->Cmd) < 6) {
504 cprintf("501 Syntax error\r\n");
508 if (strncasecmp(ChrPtr(SMTP->Cmd) + 5, "From:", 5)) {
509 cprintf("501 Syntax error\r\n");
513 StrBufAppendBuf(SMTP->from, SMTP->Cmd, 5);
514 StrBufTrim(SMTP->from);
515 if (strchr(ChrPtr(SMTP->from), '<') != NULL) {
516 StrBufStripAllBut(SMTP->from, '<', '>');
519 // We used to reject empty sender names, until it was brought to our
520 // attention that RFC1123 5.2.9 requires that this be allowed. So now
521 // we allow it, but replace the empty string with a fake
522 // address so we don't have to contend with the empty string causing
523 // other code to fail when it's expecting something there.
524 if (StrLength(SMTP->from) == 0) {
525 StrBufPlain(SMTP->from, HKEY("someone@example.com"));
528 // If this SMTP connection is from a logged-in user, force the 'from'
529 // to be the user's Internet e-mail address as Citadel knows it.
531 StrBufPlain(SMTP->from, CC->cs_inet_email, -1);
532 cprintf("250 Sender ok <%s>\r\n", ChrPtr(SMTP->from));
533 SMTP->message_originated_locally = 1;
537 else if (SMTP->is_lmtp) {
538 // Bypass forgery checking for LMTP
541 // Otherwise, make sure outsiders aren't trying to forge mail from
542 // this system (unless, of course, c_allow_spoofing is enabled)
543 else if (CtdlGetConfigInt("c_allow_spoofing") == 0) {
544 process_rfc822_addr(ChrPtr(SMTP->from), user, node, name);
545 syslog(LOG_DEBUG, "serv_smtp: claimed envelope sender is '%s' == '%s' @ '%s' ('%s')",
546 ChrPtr(SMTP->from), user, node, name
548 if (CtdlHostAlias(node) != hostalias_nomatch) {
549 cprintf("550 You must log in to send mail from %s\r\n", node);
550 FlushStrBuf(SMTP->from);
551 syslog(LOG_DEBUG, "serv_smtp: rejecting unauthenticated mail from %s", node);
556 cprintf("250 Sender ok\r\n");
560 // Implements the "RCPT To:" command
561 void smtp_rcpt(void) {
562 char message_to_spammer[SIZ];
563 struct recptypes *valid = NULL;
565 if (StrLength(SMTP->from) == 0) {
566 cprintf("503 Need MAIL before RCPT\r\n");
570 if (StrLength(SMTP->Cmd) < 6) {
571 cprintf("501 Syntax error\r\n");
575 if (strncasecmp(ChrPtr(SMTP->Cmd) + 5, "To:", 3)) {
576 cprintf("501 Syntax error\r\n");
580 if ( (SMTP->is_msa) && (!CC->logged_in) ) {
581 cprintf("550 You must log in to send mail on this port.\r\n");
582 FlushStrBuf(SMTP->from);
586 FlushStrBuf(SMTP->OneRcpt);
587 StrBufAppendBuf(SMTP->OneRcpt, SMTP->Cmd, 8);
588 StrBufTrim(SMTP->OneRcpt);
589 StrBufStripAllBut(SMTP->OneRcpt, '<', '>');
591 if ( (StrLength(SMTP->OneRcpt) + StrLength(SMTP->recipients)) >= SIZ) {
592 cprintf("452 Too many recipients\r\n");
598 (!CC->logged_in) // Don't RBL authenticated users
599 && (!SMTP->is_lmtp) // Don't RBL LMTP clients
600 && (CtdlGetConfigInt("c_rbl_at_greeting") == 0) // Don't RBL if we did it at connection time
601 && (rbl_check(CC->cs_addr, message_to_spammer))
603 cprintf("550 %s\r\n", message_to_spammer);
604 return; // no need to free_recipients(valid)
605 } // because it hasn't been allocated yet
607 // This is a *preliminary* call to validate_recipients() to evaluate one recipient.
608 valid = validate_recipients(
609 (char *)ChrPtr(SMTP->OneRcpt),
610 smtp_get_Recipients(),
611 (SMTP->is_lmtp)? POST_LMTP: (CC->logged_in)? POST_LOGGED_IN: POST_EXTERNAL
614 // Any type of error thrown by validate_recipients() will make the SMTP transaction fail at this point.
615 if (valid->num_error != 0) {
616 cprintf("550 %s\r\n", valid->errormsg);
617 free_recipients(valid);
622 (valid->num_internet > 0) // If it's outbound Internet mail...
623 && (CC->logged_in) // ...and we're a logged-in user...
624 && (CtdlCheckInternetMailPermission(&CC->user)==0) // ...who does not have Internet mail rights...
626 cprintf("551 <%s> - you do not have permission to send Internet mail\r\n", ChrPtr(SMTP->OneRcpt));
627 free_recipients(valid);
632 (valid->num_internet > 0) // If it's outbound Internet mail...
633 && (SMTP->message_originated_locally == 0) // ...and also inbound Internet mail...
634 && (SMTP->is_lmtp == 0) /// ...and didn't arrive via LMTP...
636 cprintf("551 <%s> - relaying denied\r\n", ChrPtr(SMTP->OneRcpt));
637 free_recipients(valid);
642 (valid->num_room > 0) // If it's mail to a room (mailing list)...
643 && (SMTP->message_originated_locally == 0) // ...and also inbound Internet mail...
644 && (is_email_subscribed_to_list((char *)ChrPtr(SMTP->from), valid->recp_room) == 0) // ...and not a subscriber
646 cprintf("551 <%s> - The message is not from a list member\r\n", ChrPtr(SMTP->OneRcpt));
647 free_recipients(valid);
651 cprintf("250 RCPT ok <%s>\r\n", ChrPtr(SMTP->OneRcpt));
652 if (StrLength(SMTP->recipients) > 0) {
653 StrBufAppendBufPlain(SMTP->recipients, HKEY(","), 0);
655 StrBufAppendBuf(SMTP->recipients, SMTP->OneRcpt, 0);
656 SMTP->number_of_recipients ++;
658 free_recipients(valid);
663 // Implements the DATA command
664 void smtp_data(void) {
667 struct CtdlMessage *msg = NULL;
670 struct recptypes *valid;
674 if (StrLength(SMTP->from) == 0) {
675 cprintf("503 Need MAIL command first.\r\n");
679 if (SMTP->number_of_recipients < 1) {
680 cprintf("503 Need RCPT command first.\r\n");
684 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
686 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
687 defbody = NewStrBufPlain(NULL, SIZ);
689 if (defbody != NULL) {
690 if (SMTP->is_lmtp && (CC->cs_UDSclientUID != -1)) {
693 "Received: from %s (Citadel from userid %ld)\n"
695 ChrPtr(SMTP->helo_node),
696 (long int) CC->cs_UDSclientUID,
697 CtdlGetConfigStr("c_fqdn"),
703 "Received: from %s (%s [%s])\n"
705 ChrPtr(SMTP->helo_node),
708 CtdlGetConfigStr("c_fqdn"),
712 body = CtdlReadMessageBodyBuf(HKEY("."), CtdlGetConfigLong("c_maxmsglen"), defbody, 1);
713 FreeStrBuf(&defbody);
715 cprintf("550 Unable to save message: internal error.\r\n");
719 syslog(LOG_DEBUG, "serv_smtp: converting message...");
720 msg = convert_internet_message_buf(&body);
722 // If the user is locally authenticated, FORCE the From: header to
723 // show up as the real sender. Yes, this violates the RFC standard,
724 // but IT MAKES SENSE. If you prefer strict RFC adherence over
725 // common sense, you can disable this in the configuration.
727 // We also set the "message room name" ('O' field) to MAILROOM
728 // (which is Mail> on most systems) to prevent it from getting set
729 // to something ugly like "0000058008.Sent Items>" when the message
730 // is read with a Citadel client.
732 if ( (CC->logged_in) && (CtdlGetConfigInt("c_rfc822_strict_from") != CFG_SMTP_FROM_NOFILTER) ) {
735 if (!CM_IsEmpty(msg, erFc822Addr) &&
736 ((CtdlGetConfigInt("c_rfc822_strict_from") == CFG_SMTP_FROM_CORRECT) ||
737 (CtdlGetConfigInt("c_rfc822_strict_from") == CFG_SMTP_FROM_REJECT) ) )
739 if (!IsEmptyStr(CC->cs_inet_email)) {
740 validemail = strcmp(CC->cs_inet_email, msg->cm_fields[erFc822Addr]) == 0;
742 if ((!validemail) && (!IsEmptyStr(CC->cs_inet_other_emails))) {
743 int num_secondary_emails = 0;
745 num_secondary_emails = num_tokens(CC->cs_inet_other_emails, '|');
746 for (i=0; i < num_secondary_emails && !validemail; ++i) {
748 extract_token(buf, CC->cs_inet_other_emails,i,'|',sizeof CC->cs_inet_other_emails);
749 validemail = strcmp(buf, msg->cm_fields[erFc822Addr]) == 0;
754 if (!validemail && (CtdlGetConfigInt("c_rfc822_strict_from") == CFG_SMTP_FROM_REJECT)) {
755 syslog(LOG_ERR, "serv_smtp: invalid sender '%s' - rejecting this message", msg->cm_fields[erFc822Addr]);
756 cprintf("550 Invalid sender '%s' - rejecting this message.\r\n", msg->cm_fields[erFc822Addr]);
760 CM_SetField(msg, eOriginalRoom, MAILROOM);
761 if (SMTP->preferred_sender_name != NULL)
762 CM_SetField(msg, eAuthor, ChrPtr(SMTP->preferred_sender_name));
764 CM_SetField(msg, eAuthor, CC->user.fullname);
767 if (SMTP->preferred_sender_email != NULL) {
768 CM_SetField(msg, erFc822Addr, ChrPtr(SMTP->preferred_sender_email));
771 CM_SetField(msg, erFc822Addr, CC->cs_inet_email);
776 // Set the "envelope from" address
777 CM_SetField(msg, eMessagePath, ChrPtr(SMTP->from));
779 // Set the "envelope to" address
780 CM_SetField(msg, eenVelopeTo, ChrPtr(SMTP->recipients));
782 // Submit the message into the Citadel system.
783 valid = validate_recipients(
784 (char *)ChrPtr(SMTP->recipients),
785 smtp_get_Recipients(),
786 (SMTP->is_lmtp)? POST_LMTP: (CC->logged_in)? POST_LOGGED_IN: POST_EXTERNAL
789 // If there are modules that want to scan this message before final
790 // submission (such as virus checkers or spam filters), call them now
791 // and give them an opportunity to reject the message.
792 if (SMTP->is_unfiltered) {
796 scan_errors = PerformMessageHooks(msg, valid, EVT_SMTPSCAN);
799 if (scan_errors > 0) { // We don't want this message!
801 if (CM_IsEmpty(msg, eErrorMsg)) {
802 CM_SetField(msg, eErrorMsg, "Message rejected by filter");
805 StrBufPrintf(SMTP->OneRcpt, "550 %s\r\n", msg->cm_fields[eErrorMsg]);
808 else { // Ok, we'll accept this message.
809 msgnum = CtdlSubmitMsg(msg, valid, "");
811 StrBufPrintf(SMTP->OneRcpt, "250 Message accepted.\r\n");
814 StrBufPrintf(SMTP->OneRcpt, "550 Internal delivery error\r\n");
818 // For SMTP and ESMTP, just print the result message. For LMTP, we
819 // have to print one result message for each recipient. Since there
820 // is nothing in Citadel which would cause different recipients to
821 // have different results, we can get away with just spitting out the
822 // same message once for each recipient.
824 for (i=0; i<SMTP->number_of_recipients; ++i) {
825 cputbuf(SMTP->OneRcpt);
829 cputbuf(SMTP->OneRcpt);
832 // Write something to the syslog(which may or may not be where the
833 // rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
834 syslog((LOG_MAIL | LOG_INFO),
835 "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
838 SMTP->number_of_recipients,
841 ChrPtr(SMTP->OneRcpt)
846 free_recipients(valid);
847 smtp_data_clear(); // clear out the buffers now
851 // Implements the STARTTLS command
852 void smtp_starttls(void) {
853 char ok_response[SIZ];
854 char nosup_response[SIZ];
855 char error_response[SIZ];
857 sprintf(ok_response, "220 Begin TLS negotiation now\r\n");
858 sprintf(nosup_response, "554 TLS not supported here\r\n");
859 sprintf(error_response, "554 Internal error\r\n");
860 CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
865 // Implements the NOOP (NO OPeration) command
866 void smtp_noop(void) {
867 cprintf("250 NOOP\r\n");
871 // Implements the QUIT command
872 void smtp_quit(void) {
873 cprintf("221 Goodbye...\r\n");
874 CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
878 // Main command loop for SMTP server sessions.
879 void smtp_command_loop(void) {
880 static const ConstStr AuthPlainStr = {HKEY("AUTH PLAIN")};
882 assert(SMTP != NULL);
885 if (CtdlClientGetLine(SMTP->Cmd) < 1) {
886 syslog(LOG_INFO, "SMTP: client disconnected: ending session.");
887 CC->kill_me = KILLME_CLIENT_DISCONNECTED;
891 if (SMTP->command_state == smtp_user) {
892 if (!strncmp(ChrPtr(SMTP->Cmd), AuthPlainStr.Key, AuthPlainStr.len)) {
901 else if (SMTP->command_state == smtp_password) {
906 else if (SMTP->command_state == smtp_plain) {
911 syslog(LOG_DEBUG, "serv_smtp: client sent command <%s>", ChrPtr(SMTP->Cmd));
913 if (!strncasecmp(ChrPtr(SMTP->Cmd), "NOOP", 4)) {
918 if (!strncasecmp(ChrPtr(SMTP->Cmd), "QUIT", 4)) {
923 if (!strncasecmp(ChrPtr(SMTP->Cmd), "HELO", 4)) {
928 if (!strncasecmp(ChrPtr(SMTP->Cmd), "EHLO", 4)) {
933 if (!strncasecmp(ChrPtr(SMTP->Cmd), "LHLO", 4)) {
938 if (!strncasecmp(ChrPtr(SMTP->Cmd), "RSET", 4)) {
943 if (!strncasecmp(ChrPtr(SMTP->Cmd), "AUTH", 4)) {
948 if (!strncasecmp(ChrPtr(SMTP->Cmd), "DATA", 4)) {
953 if (!strncasecmp(ChrPtr(SMTP->Cmd), "HELP", 4)) {
958 if (!strncasecmp(ChrPtr(SMTP->Cmd), "MAIL", 4)) {
963 if (!strncasecmp(ChrPtr(SMTP->Cmd), "RCPT", 4)) {
968 if (!strncasecmp(ChrPtr(SMTP->Cmd), "STARTTLS", 8)) {
974 cprintf("502 I'm afraid I can't do that.\r\n");
978 // *****************************************************************************
979 // * MODULE INITIALIZATION STUFF *
980 // *****************************************************************************
982 // This cleanup function blows away the temporary memory used by
984 void smtp_cleanup_function(void) {
985 // Don't do this stuff if this is not an SMTP session!
986 if (CC->h_command_function != smtp_command_loop) return;
988 syslog(LOG_DEBUG, "Performing SMTP cleanup hook");
990 FreeStrBuf(&SMTP->Cmd);
991 FreeStrBuf(&SMTP->helo_node);
992 FreeStrBuf(&SMTP->from);
993 FreeStrBuf(&SMTP->recipients);
994 FreeStrBuf(&SMTP->OneRcpt);
995 FreeStrBuf(&SMTP->preferred_sender_email);
996 FreeStrBuf(&SMTP->preferred_sender_name);
1001 const char *CitadelServiceSMTP_MTA="SMTP-MTA";
1002 const char *CitadelServiceSMTPS_MTA="SMTPs-MTA";
1003 const char *CitadelServiceSMTP_MSA="SMTP-MSA";
1004 const char *CitadelServiceSMTP_LMTP="LMTP";
1005 const char *CitadelServiceSMTP_LMTP_UNF="LMTP-UnF";
1008 // Initialization function, called from modules_init.c
1009 char *ctdl_module_init_smtp(void) {
1011 CtdlRegisterServiceHook(CtdlGetConfigInt("c_smtp_port"), // SMTP MTA
1016 CitadelServiceSMTP_MTA);
1019 CtdlRegisterServiceHook(CtdlGetConfigInt("c_smtps_port"), // SMTPS MTA
1024 CitadelServiceSMTPS_MTA);
1027 CtdlRegisterServiceHook(CtdlGetConfigInt("c_msa_port"), // SMTP MSA
1032 CitadelServiceSMTP_MSA);
1034 CtdlRegisterServiceHook(0, // local LMTP
1039 CitadelServiceSMTP_LMTP);
1041 CtdlRegisterServiceHook(0, // local LMTP
1042 file_lmtp_unfiltered_socket,
1043 lmtp_unfiltered_greeting,
1046 CitadelServiceSMTP_LMTP_UNF);
1048 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP, PRIO_STOP + 250);
1051 // return our module name for the log