20000417
The SASL authentication in the SMTP server and client works,
- but only on Linux and Solaris.
+ but only on Linux and Solaris, neither of which I wish to
+ run on my laptop.
+
+20000418
+
+ Added LMTP support to the smtp-source and smtp-sink utilities
+ so that I don't have to install Cyrus IMAP just to test
+ LMTP.
===============================================================
Do not use this code. The Postfix SASL support is based on the
-Cyrus SASL library, which is light years away from production
-quality. There is not enough documentation to figure out how the
-software is supposed to work.
+Cyrus SASL library, which has not enough documentation about how
+the software is supposed to work.
-The SASL library code works only on LINUX and Solaris. If you
-build Postfix+SASL on other systems, the software builds without
-trouble but fails at runtime due to no available authentication
-mechanisms. It can be made to work with considerable tweaking.
+Postfix+SASL 1.5.5 appears to work on RedHat 6.1 (pwcheck_method
+of shadow or sasldb), Solaris 2.7 (pwcheck_method of shadow or
+sasldb), and FreeBSD 3.4 (pwcheck_method of sasldb). On RedHat
+6.1, SASL 1.5.5 needed write access to the sasldb file.
+
+SASL is a lot of complex code. In a future version the Postfix SASL
+code is likely to be put outside the SMTP server.
Introduction
============
Till Franke of SuSE Rhein/Main AG. The present code is a trimmed-down
version with only the bare necessities.
-When receiving mail, Postfix logs the client-provided username and
-sender address to the maillog file, and optionally grants mail
-relay access to authenticated clients. SASL authentication information
-is not passed on via message headers or via SMTP. It is no-one's
-business what username and authentication method the poster was
-using in order to access the mail server.
+When receiving mail, Postfix logs the client-provided username,
+authentication method, and sender address to the maillog file, and
+optionally grants mail access via the permit_sasl_authenticated
+UCE restriction. SASL authentication information is not passed on
+via message headers or via SMTP. It is no-one's business what
+username and authentication method the poster was using in order
+to access the mail server.
-When sending mail, Postfix looks up the server hostname and if
-a username/password is known, it will use that to authenticate
-to the server.
+When sending mail, Postfix looks up the server hostname in a table,
+and if a username/password is found, it will use that username and
+password to authenticate to the server.
Building the SASL library
=========================
In /usr/local/lib/sasl/smtpd.conf you need to specify what authentication
mechanism the server will support, for example:
- pwcheck_method: shadow
-
-This will use the Linux or Solaris shadow passwd file, which is
-the only way that I was able to test, but which is undesirable
-because it uses plaintext passwords.
+ pwcheck_method: sasldb
-If you wish to use the system shadow password file, the Postfix
-SMTP server can't run chrooted (see master.cf), and the postfix
-user or group needs read access to the shadow passwd file.
+This will use the SASL password file (default: /etc/sasldb), which
+is maintained with the saslpasswd command. On some systems the
+saslpasswd command needs to be run multiple times before it stops
+complaining. The Postfix SMTP server needs read access to the
+sasldb file - you have to play games with group access permissions.
To run chrooted with SASL support is an interesting exercise.
smtp_sasl_passwd_maps = hash:/etc/postfix/sasl_passwd
/etc/postfix/sasl_passwd:
- host.domain username:password
- host.domain username
+ foo.com username:password
+ bar.com username
The SASL password file is opened before the SMTP server enters the
optional chroot jail, so there is no need to copy the sasl_passwd
* Version of this program.
*/
#define VAR_MAIL_VERSION "mail_version"
-#define DEF_MAIL_VERSION "Snapshot-20000417"
+#define DEF_MAIL_VERSION "Snapshot-20000418"
extern char *var_mail_version;
/* LICENSE
/* SMTP_STATE *state;
/* DESCRIPTION
/* This module contains random chunks of code that implement
-/* the SMTP protocol interface for SASL negotiation. The goal
+/* the SMTP protocol interface for SASL negotiation. The goal
/* is to reduce clutter in the main SMTP client source code.
/*
/* smtp_sasl_helo_auth() processes the AUTH option in the
/* smtp_sasl_helo_auth - handle AUTH option in EHLO reply */
-void smtp_sasl_helo_auth(SMTP_STATE *state, const char *words)
+void smtp_sasl_helo_auth(SMTP_STATE *state, const char *words)
{
/*
if (strlen(words) > 0) {
state->sasl_mechanism_list = mystrdup(words);
state->features |= SMTP_FEATURE_AUTH;
+ } else {
+ msg_warn("%s offered null AUTH mechanism list",
+ state->session->namaddr);
}
}
/* smtp_sasl_helo_login - perform SASL login */
-int smtp_sasl_helo_login(SMTP_STATE *state)
+int smtp_sasl_helo_login(SMTP_STATE *state)
{
VSTRING *why = vstring_alloc(10);
int ret = 0;
/*
- * Skip authentication when we have no authentication info for this
- * server. In that case it should simply treat us like any stranger.
- * Otherwise, if authentication fails assume the error is recoverable.
+ * Skip authentication when no authentication info exists for this
+ * server, so that we talk to each other like strangers. Otherwise, if
+ * authentication information exists, assume that authentication is
+ * required, and assume that an authentication error is recoverable.
*/
if (smtp_sasl_passwd_lookup(state) != 0) {
smtp_sasl_start(state);
/* This member is a null pointer in the absence of successful
/* authentication.
/* .PP
-/* smtpd_sasl_logout() cleant up after smtpd_sasl_authenticate().
+/* smtpd_sasl_logout() cleans up after smtpd_sasl_authenticate().
/* This routine exists for the sake of symmetry.
/*
/* smtpd_sasl_disconnect() performs per-connection cleanup.
msg_fatal("SASL per-connection server initialization");
/*
- * Security options. XXX What exactly is this supposed to be doing? The
- * cyrus-sasl-1.5.15 source code has no documentation at all about this
- * routine.
- *
- * Disallow anonymous authentication. The permit_sasl_authenticated feature
- * is restricted to authenticated clients only.
+ * Security options. Some information can be found in the sasl.h include
+ * file. Disallow anonymous authentication; this is because the
+ * permit_sasl_authenticated feature is restricted to authenticated
+ * clients only.
*/
memset(&sec_props, 0, sizeof(sec_props));
sec_props.min_ssf = 0;
dec_buffer = STR(state->sasl_decoded);
if (sasl_decode64(init_response, reply_len,
dec_buffer, &dec_length) != SASL_OK)
- return ("501 AUTH failed: malformed initial response");
+ return ("501 Authentication failed: malformed initial response");
if (msg_verbose)
msg_info("%s: decoded initial response %s", myname, dec_buffer);
} else {
* comes in multiples of four bytes for each triple of input bytes,
* plus four bytes for any incomplete last triple, plus one byte for
* the null terminator.
+ *
+ * XXX Replace the klunky sasl_encode64() interface by something that
+ * uses VSTRING buffers.
*/
if (msg_verbose)
msg_info("%s: uncoded challenge: %.*s",
}
/*
- * Cleanup. What a horrible interface.
+ * Cleanup. What an awful interface.
*/
if (serverout)
free(serverout);
/* NAME
/* smtp-sink 8
/* SUMMARY
-/* multi-threaded smtp test server
+/* multi-threaded SMTP/LMTP test server
/* SYNOPSIS
-/* smtp-sink [-c] [-p] [-v] [-w delay] [host]:port backlog
+/* smtp-sink [-cLpv] [-w delay] [host]:port backlog
/* DESCRIPTION
/* \fIsmtp-sink\fR listens on the named host (or address) and port.
/* It takes SMTP messages from the network and throws them away.
/* .IP -c
/* Display a running counter that is updated whenever an SMTP
/* QUIT command is executed.
+/* .IP -L
+/* Speak LMTP rather than SMTP.
/* .IP -p
/* Disable ESMTP command pipelining.
/* .IP -v
/* .IP "-w delay"
/* Wait \fIdelay\fR seconds before responding to a DATA command.
/* SEE ALSO
-/* smtp-source, SMTP test message generator
+/* smtp-source, SMTP/LMTP test message generator
/* LICENSE
/* .ad
/* .fi
VSTREAM *stream;
int data_state;
int (*read) (struct SINK_STATE *);
+ int rcpts;
} SINK_STATE;
#define ST_ANY 0
static int counter;
static int disable_pipelining;
static int fixed_delay;
+static int enable_lmtp;
/* ehlo_response - respond to EHLO command */
smtp_printf(state->stream, "250 Ok");
}
+/* mail_response - reset recipient count, send 250 OK */
+
+static void mail_response(SINK_STATE *state)
+{
+ state->rcpts = 0;
+ ok_response(state);
+}
+
+/* rcpt_response - bump recipient count, send 250 OK */
+
+static void rcpt_response(SINK_STATE *state)
+{
+ state->rcpts++;
+ ok_response(state);
+}
+
/* data_response - respond to DATA command */
static void data_response(SINK_STATE *state)
data_response(state);
}
+/* dot_response - response to . command */
+
+static void dot_response(SINK_STATE *state)
+{
+ if (enable_lmtp) {
+ while (state->rcpts-- > 0) /* XXX this could block */
+ ok_response(state);
+ } else {
+ ok_response(state);
+ }
+}
+
/* quit_response - respond to QUIT command */
static void quit_response(SINK_STATE *state)
if (state->data_state == ST_CR_LF_DOT_CR_LF) {
if (msg_verbose)
msg_info(".");
- ok_response(state);
+ dot_response(state);
state->read = command_read;
break;
}
static SINK_COMMAND command_table[] = {
"helo", ok_response,
"ehlo", ehlo_response,
- "mail", ok_response,
- "rcpt", ok_response,
+ "lhlo", ehlo_response,
+ "mail", mail_response,
+ "rcpt", rcpt_response,
"data", data_response,
"rset", ok_response,
"noop", ok_response,
static void usage(char *myname)
{
- msg_fatal("usage: %s [-c] [-p] [-v] [host]:port backlog", myname);
+ msg_fatal("usage: %s [-cLpv] [host]:port backlog", myname);
}
int main(int argc, char **argv)
/*
* Parse JCL.
*/
- while ((ch = GETOPT(argc, argv, "cpvw:")) > 0) {
+ while ((ch = GETOPT(argc, argv, "cLpvw:")) > 0) {
switch (ch) {
case 'c':
count++;
break;
+ case 'L':
+ enable_lmtp = 1;
+ break;
case 'p':
disable_pipelining = 1;
break;
/* Old mode: don't send HELO, and don't send message headers.
/* .IP "-l length"
/* Send \fIlength\fR bytes as message payload.
+/* .IP -L
+/* Speak LMTP rather than SMTP.
/* .IP "-m message_count"
/* Send the specified number of messages (default: 1).
/* .IP "-r recipient_count"
*/
typedef struct SESSION {
int xfer_count; /* # of xfers in session */
+ int rcpt_done; /* # of recipients done */
int rcpt_count; /* # of recipients to go */
VSTREAM *stream; /* open connection */
int connect_count; /* # of connect()s to retry */
static int connect_count = 1;
static int random_delay = 0;
static int fixed_delay = 0;
+static int talk_lmtp = 0;
static void enqueue_connect(SESSION *);
static void start_connect(SESSION *);
static void send_helo(SESSION *session)
{
int except;
+ char *protocol = (talk_lmtp ? "LHLO" : "EHLO");
/*
* Send the standard greeting with our hostname
if ((except = setjmp(smtp_timeout_buf)) != 0)
msg_fatal("%s while sending HELO", exception_text(except));
- command(session->stream, "HELO %s", var_myhostname);
+ command(session->stream, "%s %s", protocol, var_myhostname);
/*
* Prepare for the next event.
msg_fatal("sender rejected: %d %s", resp->code, resp->str);
session->rcpt_count = recipients;
+ session->rcpt_done = 0;
send_rcpt(unused, context);
}
else
command(session->stream, "RCPT TO:<%s>", recipient);
session->rcpt_count--;
+ session->rcpt_done++;
/*
* Prepare for the next event.
*/
if ((except = setjmp(smtp_timeout_buf)) != 0)
msg_fatal("%s while sending message", exception_text(except));
- if ((resp = response(session->stream, buffer))->code / 100 != 2)
- msg_fatal("data %d %s", resp->code, resp->str);
+ do { /* XXX this could block */
+ if ((resp = response(session->stream, buffer))->code / 100 != 2)
+ msg_fatal("data %d %s", resp->code, resp->str);
+ } while (talk_lmtp && --session->rcpt_done > 0);
session->xfer_count++;
/*
/*
* Parse JCL.
*/
- while ((ch = GETOPT(argc, argv, "cC:df:l:m:or:R:s:t:vw:")) > 0) {
+ while ((ch = GETOPT(argc, argv, "cC:df:l:Lm:or:R:s:t:vw:")) > 0) {
switch (ch) {
case 'c':
count++;
message_data[i - 1] = '\n';
}
break;
+ case 'L':
+ talk_lmtp = 1;
+ break;
case 'm':
if ((message_count = atoi(optarg)) <= 0)
usage(argv[0]);