/*
* $Id$
*
- * An implementation of RFC821 (Simple Mail Transfer Protocol) for the
- * Citadel system.
+ * This module is an SMTP and ESMTP implementation for the Citadel system.
+ * It is compliant with all of the following:
+ *
+ * RFC 821 - Simple Mail Transfer Protocol
+ * RFC 876 - Survey of SMTP Implementations
+ * RFC 1047 - Duplicate messages and SMTP
+ * RFC 1854 - command pipelining
+ * RFC 1869 - Extended Simple Mail Transfer Protocol
+ * RFC 1870 - SMTP Service Extension for Message Size Declaration
+ * RFC 1893 - Enhanced Mail System Status Codes
+ * RFC 2033 - Local Mail Transfer Protocol
+ * RFC 2034 - SMTP Service Extension for Returning Enhanced Error Codes
+ * RFC 2197 - SMTP Service Extension for Command Pipelining
+ * RFC 2554 - SMTP Service Extension for Authentication
+ * RFC 2821 - Simple Mail Transfer Protocol
+ * RFC 2822 - Internet Message Format
+ * RFC 2920 - SMTP Service Extension for Command Pipelining
*
*/
struct citsmtp { /* Information about the current session */
int command_state;
char helo_node[SIZ];
- struct usersupp vrfy_buffer;
+ struct ctdluser vrfy_buffer;
int vrfy_count;
char vrfy_match[SIZ];
char from[SIZ];
char recipients[SIZ];
int number_of_recipients;
- int number_of_rooms;
int delivery_mode;
int message_originated_locally;
+ int is_lmtp;
};
enum { /* Command states for login authentication */
#define SMTP_RECPS ((char *)CtdlGetUserData(SYM_SMTP_RECPS))
#define SMTP_ROOMS ((char *)CtdlGetUserData(SYM_SMTP_ROOMS))
-long SYM_SMTP;
-long SYM_SMTP_RECPS;
-long SYM_SMTP_ROOMS;
int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
cprintf("220 %s ESMTP Citadel/UX server ready.\r\n", config.c_fqdn);
}
+/*
+ * LMTP is like SMTP but with some extra bonus footage added.
+ */
+void lmtp_greeting(void) {
+ smtp_greeting();
+ SMTP->is_lmtp = 1;
+}
+
/*
* Implement HELO and EHLO commands.
+ *
+ * which_command: 0=HELO, 1=EHLO, 2=LHLO
*/
-void smtp_hello(char *argbuf, int is_esmtp) {
+void smtp_hello(char *argbuf, int which_command) {
safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
- if (!is_esmtp) {
- cprintf("250 Greetings and joyous salutations.\r\n");
+ if ( (which_command != 2) && (SMTP->is_lmtp) ) {
+ cprintf("500 Only LHLO is allowed when running LMTP\r\n");
+ return;
+ }
+
+ if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
+ cprintf("500 LHLO is only allowed when running LMTP\r\n");
+ return;
+ }
+
+ if (which_command == 0) {
+ cprintf("250 Hello %s (%s [%s])\r\n",
+ SMTP->helo_node,
+ CC->cs_host,
+ CC->cs_addr
+ );
}
else {
- cprintf("250-Greetings and joyous salutations.\r\n");
+ if (which_command == 1) {
+ cprintf("250-Hello %s (%s [%s])\r\n",
+ SMTP->helo_node,
+ CC->cs_host,
+ CC->cs_addr
+ );
+ }
+ else {
+ cprintf("250-Greetings and joyous salutations.\r\n");
+ }
cprintf("250-HELP\r\n");
cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
cprintf("250-PIPELINING\r\n");
}
+
/*
* Implement HELP command.
*/
CtdlDecodeBase64(password, argbuf, SIZ);
lprintf(9, "Trying <%s>\n", password);
if (CtdlTryPassword(password) == pass_ok) {
- cprintf("235 2.0.0 Hello, %s\r\n", CC->usersupp.fullname);
+ cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
lprintf(9, "SMTP authenticated login successful\n");
CC->internal_pgm = 0;
CC->cs_flags &= ~CS_STEALTH;
}
else {
- cprintf("500 5.7.0 Authentication failed.\r\n");
+ cprintf("535 5.7.0 Authentication failed.\r\n");
}
SMTP->command_state = smtp_command;
}
char buf[SIZ];
if (strncasecmp(argbuf, "login", 5) ) {
- cprintf("550 5.7.4 We only support LOGIN authentication.\r\n");
+ cprintf("504 5.7.4 We only support LOGIN authentication.\r\n");
return;
}
/*
* Back end for smtp_vrfy() command
*/
-void smtp_vrfy_backend(struct usersupp *us, void *data) {
+void smtp_vrfy_backend(struct ctdluser *us, void *data) {
if (!fuzzy_match(us, SMTP->vrfy_match)) {
++SMTP->vrfy_count;
- memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
+ memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
}
}
/*
* Back end for smtp_expn() command
*/
-void smtp_expn_backend(struct usersupp *us, void *data) {
+void smtp_expn_backend(struct ctdluser *us, void *data) {
if (!fuzzy_match(us, SMTP->vrfy_match)) {
}
++SMTP->vrfy_count;
- memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
+ memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
}
}
* be sure to phree() them first!
*/
void smtp_rset(void) {
+ int is_lmtp;
+
+ /*
+ * Our entire SMTP state is discarded when a RSET command is issued,
+ * but we need to preserve this one little piece of information, so
+ * we save it for later.
+ */
+ is_lmtp = SMTP->is_lmtp;
+
memset(SMTP, 0, sizeof(struct citsmtp));
/*
* }
*/
+ /*
+ * Reinstate this little piece of information we saved (see above).
+ */
+ SMTP->is_lmtp = is_lmtp;
+
cprintf("250 2.0.0 Zap!\r\n");
}
stripallbut(SMTP->from, '<', '>');
/* We used to reject empty sender names, until it was brought to our
- * attention that RFC1123 5.2.9 requires that this be allowed.
+ * attention that RFC1123 5.2.9 requires that this be allowed. So now
+ * we allow it, but replace the empty string with a fake
+ * address so we don't have to contend with the empty string causing
+ * other code to fail when it's expecting something there.
+ */
if (strlen(SMTP->from) == 0) {
- cprintf("501 5.1.7 Empty sender name is not permitted\r\n");
- return;
+ strcpy(SMTP->from, "someone@somewhere.org");
}
- */
/* If this SMTP connection is from a logged-in user, force the 'from'
* to be the user's Internet e-mail address as Citadel knows it.
return;
}
+ else if (SMTP->is_lmtp) {
+ /* Bypass forgery checking for LMTP */
+ }
+
/* Otherwise, make sure outsiders aren't trying to forge mail from
* this system.
*/
}
/* RBL check */
- if ( (!CC->logged_in) && (!CC->is_local_socket) ) {
+ if ( (!CC->logged_in)
+ && (!SMTP->is_lmtp) ) {
if (rbl_check(message_to_spammer)) {
cprintf("550 %s\r\n", message_to_spammer);
/* no need to phree(valid), it's not allocated yet */
}
if (valid->num_internet > 0) {
- if (SMTP->message_originated_locally == 0) {
+ if ( (SMTP->message_originated_locally == 0)
+ && (SMTP->is_lmtp == 0) ) {
cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
phree(valid);
return;
char nowstamp[SIZ];
struct recptypes *valid;
int scan_errors;
+ int i;
+ char result[SIZ];
if (strlen(SMTP->from) == 0) {
cprintf("503 5.5.1 Need MAIL command first.\r\n");
datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
body = mallok(4096);
- /* FIXME
- it should be Received: from %s (real.name.dom [w.x.y.z])
- */
if (body != NULL) snprintf(body, 4096,
- "Received: from %s (%s)\n"
+ "Received: from %s (%s [%s])\n"
" by %s; %s\n",
SMTP->helo_node,
CC->cs_host,
+ CC->cs_addr,
config.c_fqdn,
nowstamp);
if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
if (msg->cm_fields['F'] != NULL) phree(msg->cm_fields['F']);
if (msg->cm_fields['O'] != NULL) phree(msg->cm_fields['O']);
- msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
+ msg->cm_fields['A'] = strdoop(CC->user.fullname);
msg->cm_fields['N'] = strdoop(config.c_nodename);
msg->cm_fields['H'] = strdoop(config.c_humannode);
msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
"5.7.1 Message rejected by filter");
}
- cprintf("550 %s\r\n", msg->cm_fields['0']);
+ sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
}
else { /* Ok, we'll accept this message. */
msgnum = CtdlSubmitMsg(msg, valid, "");
if (msgnum > 0L) {
- cprintf("250 2.0.0 Message accepted.\r\n");
+ sprintf(result, "250 2.0.0 Message accepted.\r\n");
}
else {
- cprintf("550 5.5.0 Internal delivery error\r\n");
+ sprintf(result, "550 5.5.0 Internal delivery error\r\n");
}
}
+ /* For SMTP and ESTMP, just print the result message. For LMTP, we
+ * have to print one result message for each recipient. Since there
+ * is nothing in Citadel which would cause different recipients to
+ * have different results, we can get away with just spitting out the
+ * same message once for each recipient.
+ */
+ if (SMTP->is_lmtp) {
+ for (i=0; i<SMTP->number_of_recipients; ++i) {
+ cprintf("%s", result);
+ }
+ }
+ else {
+ cprintf("%s", result);
+ }
+
CtdlFreeMessage(msg);
phree(valid);
smtp_data_clear(); /* clear out the buffers now */
smtp_data();
}
- else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
- smtp_hello(&cmdbuf[5], 1);
- }
-
else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
smtp_expn(&cmdbuf[5]);
}
smtp_hello(&cmdbuf[5], 0);
}
+ else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
+ smtp_hello(&cmdbuf[5], 1);
+ }
+
+ else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
+ smtp_hello(&cmdbuf[5], 2);
+ }
+
else if (!strncasecmp(cmdbuf, "HELP", 4)) {
smtp_help();
}
*/
lprintf(7, "SMTP: processing outbound queue\n");
- if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
+ if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
return;
}
}
else {
- cprintf("%d Invalid command.\n", ERROR+ILLEGAL_VALUE);
+ cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
}
}
* Initialize the SMTP outbound queue
*/
void smtp_init_spoolout(void) {
- struct quickroom qrbuf;
+ struct ctdlroom qrbuf;
/*
* Create the room. This will silently fail if the room already
char *serv_smtp_init(void)
{
- SYM_SMTP = CtdlGetDynamicSymbol();
-
CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
NULL,
smtp_greeting,
smtp_command_loop);
CtdlRegisterServiceHook(0, /* ...and locally */
- "smtp.socket",
- smtp_greeting,
+ "lmtp.socket",
+ lmtp_greeting,
smtp_command_loop);
smtp_init_spoolout();