#undef KPOP_PRINCIPAL
/*
+ * Define this to use the Cyrus SASL library for authentication of
+ * POP and SMTP
+ */
+#undef CYRUS_SASL
+
+/*
* Define this to compile support for using Hesiod to locate
* pop servers into inc and msgchk. Do not change this value
* manually. You must run configure with the option
#undef KPOP_PRINCIPAL
/*
+ * Define this to use the Cyrus SASL library for authentication of
+ * POP and SMTP
+ */
+#undef CYRUS_SASL
+
+/*
* Define this to compile support for using Hesiod to locate
* pop servers into inc and msgchk. Do not change this value
* manually. You must run configure with the option
editorpath="$with_editor"
fi
+dnl Do you want client-side support for using SASL for authentication?
+dnl Note that this code will be enabled for both POP and SMTP
+AC_ARG_WITH(cyrus-sasl,
+[ --with-cyrus-sasl=<dir> Specify location of Cyrus SASL library for auth])
+if test x"$with_cyrus_sasl" != x -a x"$with_cyrus_sasl" != "no"; then
+ AC_DEFINE(CYRUS_SASL)dnl
+fi
+
dnl What method of posting should post use?
undefine([mts])dnl
AC_ARG_WITH(mts,
AC_SUBST(KRB4_INCLUDES)dnl
AC_SUBST(KRB4_LIBS)dnl
+dnl --------------------
+dnl CHECK FOR CYRUS SASL
+dnl --------------------
+if test x"$with_cyrus_sasl" != x -a x"$with_cyrus_sasl" != "no"; then
+ if test x"$with_cyrus_sasl" != x"yes"; then
+ SASL_INCLUDES="-I$with_cyrus_sasl/include"
+ SASL_LIBS="-L$with_cyrus_sasl/lib"
+ fi
+ save_LDFLAGS="$LDFLAGS"
+ LDFLAGS="$LDFLAGS $SASL_LIBS"
+ AC_CHECK_LIB(sasl, sasl_client_new,
+ [SASL_LIBS="$SASL_LIBS -lsasl"],
+ [AC_MSG_ERROR(Cyrus SASL library not found)])
+ LDFLAGS="$save_LDFLAGS"
+fi
+AC_SUBST(SASL_INCLUDES)dnl
+AC_SUBST(SASL_LIBS)dnl
+
dnl ---------------------
dnl CHECK TERMCAP LIBRARY
dnl ---------------------
int pop_exists (int (*)());
#endif
-int pop_init (char *, char *, char *, int, int, int);
+int pop_init (char *, char *, char *, int, int, int, int, char *);
int pop_fd (char *, int, char *, int);
int pop_stat (int *, int *);
int pop_retr (int, int (*)());
\%[\-pack\ file]
\%[\-nopack]
\%[\-kpop]
+\%[\-sasl]
+\%[\-saslmech\ mechanism]
.br
%nmhendpop%
\%[\-version]
\fIinc\fR to use Kerberized POP rather than standard POP3 on a given invocation.
If POPSERVICE was also #defined to "kpop", \fIinc\fR will be hardwired to always
use KPOP.
+
+If nmh has been compiled with SASL support, the `\-sasl' switch will enable
+the use of SASL authentication. Depending on the SASL mechanism used, this
+may require an additional password prompt from the user (but the
+\*(lq.netrc\*(rq file can be used to store this password). The
+`\-saslmech' switch can be used to select a particular SASL mechanism.
+
+If SASL authentication is successful, \fIinc\fR will attempt to negotiate
+a security layer for session encryption. Encrypted traffic is labelled
+with `(encrypted)' and `(decrypted)' when viewing the POP transaction
+with the `\-snoop' switch.
%nmhendpop%
.Fi
^$HOME/\&.mh\(ruprofile~^The user profile
\%[\-host\ hostname]
\%[\-user\ username]
\%[\-kpop]
+\%[\-sasl]
+\%[\-saslmech \mechanism]
.br
%nmhendpop%
\%[users\ ...]
\fImsgchk\fR to use Kerberized POP rather than standard POP3 on a given
invocation. If POPSERVICE was also #defined to "kpop", \fImsgchk\fR will be
hardwired to always use KPOP.
+
+If nmh has been compiled with SASL support, the `\-sasl' switch will enable
+the use of SASL authentication. Depending on the SASL mechanism used, this
+may require an additional password prompt from the user (but the
+\*(lq.netrc\*(rq file can be used to store this password). The
+`\-saslmech' switch can be used to select a particular SASL mechanism.
+
+If SASL authentication is successful, \fIinc\fR will attempt to negotiate
+a security layer for session encryption. Encrypted traffic is labelled
+with `(encrypted)' and `(decrypted)' when viewing the POP transaction
+with the `\-snoop' switch.
%nmhendpop%
.Fi
^$HOME/\&.mh\(ruprofile~^The user profile
\%[\-noverbose]
\%[\-watch] \%[\-nowatch]
\%[\-width\ columns]
+\%[\-sasl] \%[\-saslmech\ mechanism] \%[\-user\ username]
.br
file
\%[\-version]
"From:". Note that your MTA may still reveal your real identity (e.g.
sendmail's "X\-Authentication\-Warning:" header).
+If nmh has been compiled with SASL support, the `\-sasl' switch will enable
+the use of SASL authentication with the SMTP MTA. Depending on the
+SASL mechanism used, this may require an additional password prompt from the
+user (but the \*(lq.netrc\*(rq file can be used to store this password).
+`\-saslmech' switch can be used to select a particular SASL mechanism,
+and the the `\-user' switch can be used to select a authorization userid
+to provide to SASL other than the default.
+
+Currently SASL security layers are not supported for SMTP. The SASL
+SMTP code in nmh will always negotiate an unencrypted connection.
+
.Fi
^%etcdir%/mts.conf~^nmh mts configuration file
^%etcdir%/MailAliases~^global nmh alias file
.br
\%[\-verbose] \%[\-noverbose]
\%[\-watch] \%[\-nowatch]
+\%[\-sasl] \%[\-saslmech\ mechanism] \%[\-user\ username]
.br
\%[\-width\ columns]
\%[file\ ...]
By using the `\-width\ columns' switch, the user can direct \fIsend\fR
as to how long it should make header lines containing addresses.
+If nmh has been compiled with SASL support, the `\-sasl' switch will enable
+the use of SASL authentication with the SMTP MTA. Depending on the
+SASL mechanism used, this may require an additional password prompt from the
+user (but the \*(lq.netrc\*(rq file can be used to store this password).
+`\-saslmech' switch can be used to select a particular SASL mechanism,
+and the the `\-user' switch can be used to select a authorization userid
+to provide to SASL other than the default.
+
+Currently SASL security layers are not supported for SMTP. The SASL
+SMTP code in nmh will always negotiate an unencrypted connection.
+
The files specified by the profile entry \*(lqAliasfile:\*(rq and any
additional alias files given by the `\-alias aliasfile' switch will be
read (more than one file, each preceded by `\-alias', can be named).
int
sm_init (char *client, char *server, int watch, int verbose,
- int debug, int onex, int queued)
+ int debug, int onex, int queued, int sasl, char *saslmech, char *user)
{
int i, result, vecp;
int pdi[2], pdo[2];
client = LocalName(); /* no clientname -> LocalName */
}
+ if (sasl)
+ return sm_ierror("SASL authentication not supported with the "
+ "Sendmail MTA");
+
#ifdef ZMAILER
if (client == NULL || *client == '\0')
client = "localhost";
CC = @CC@
CFLAGS = @CFLAGS@
DEFS = @DEFS@
-INCLUDES = -I../.. -I$(srcdir) -I$(top_srcdir)
+SASL_INCLUDES = @SASL_INCLUDES@
+INCLUDES = -I../.. -I$(srcdir) -I$(top_srcdir) $(SASL_INCLUDES)
LORDER = @LORDER@
TSORT = @TSORT@
-
/*
* smtp.c -- nmh SMTP interface
*
return NOTOK;
}
+#ifdef CYRUS_SASL
+#include <sasl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <errno.h>
+#endif /* CYRUS_SASL */
int
sm_winit (int mode, char *from)
#endif /* MPOP */
+#ifdef CYRUS_SASL
+/*
+ * This function implements SASL authentication for SMTP. If this function
+ * completes successfully, then authentication is successful and we've
+ * (optionally) negotiated a security layer.
+ *
+ * Right now we don't support session encryption.
+ */
+static int
+sm_auth_sasl(char *user, char *mechlist, char *host)
+{
+ int result, status, outlen;
+ unsigned int buflen;
+ char *buf, outbuf[BUFSIZ];
+ const char *chosen_mech;
+ sasl_security_properties_t secprops;
+ sasl_external_properties_t extprops;
+ sasl_ssf_t *ssf;
+ int *outbufmax;
+
+ /*
+ * Initialize the callback contexts
+ */
+
+ if (user == NULL)
+ user = getusername();
+
+ callbacks[SM_SASL_N_CB_USER].context = user;
+
+ /*
+ * This is a _bit_ of a hack ... but if the hostname wasn't supplied
+ * to us on the command line, then call getpeername and do a
+ * reverse-address lookup on the IP address to get the name.
+ */
+
+ if (!host) {
+ struct sockaddr_in sin;
+ int len = sizeof(sin);
+ struct hostent *hp;
+
+ if (getpeername(fileno(sm_wfp), (struct sockaddr *) &sin, &len) < 0) {
+ sm_ierror("getpeername on SMTP socket failed: %s",
+ strerror(errno));
+ return NOTOK;
+ }
+
+ if ((hp = gethostbyaddr((void *) &sin.sin_addr, sizeof(sin.sin_addr),
+ sin.sin_family)) == NULL) {
+ sm_ierror("DNS lookup on IP address %s failed",
+ inet_ntoa(sin.sin_addr));
+ return NOTOK;
+ }
+
+ host = strdup(hp->h_name);
+ }
+
+ sasl_pw_context[0] = host;
+ sasl_pw_context[1] = user;
+
+ callbacks[SM_SASL_N_CB_PASS].context = sasl_pw_context;
+
+ result = sasl_client_init(callbacks);
+
+ if (result != SASL_OK) {
+ sm_ierror("SASL library initialization failed: %s",
+ sasl_errstring(result, NULL, NULL));
+ return NOTOK;
+ }
+
+ result = sasl_client_new("smtp", host, NULL, SASL_SECURITY_LAYER, &conn);
+
+ if (result != SASL_OK) {
+ sm_ierror("SASL client initialization failed: %s",
+ sasl_errstring(result, NULL, NULL));
+ return NOTOK;
+ }
+
+ /*
+ * Initialize the security properties
+ */
+
+ memset(&secprops, 0, sizeof(secprops));
+ secprops.maxbufsize = BUFSIZ;
+ secprops.max_ssf = 0; /* XXX change this when we do encryption */
+ memset(&extprops, 0, sizeof(extprops));
+
+ result = sasl_setprop(conn, SASL_SEC_PROPS, &secprops);
+
+ if (result != SASL_OK) {
+ sm_ierror("SASL security property initialization failed: %s",
+ sasl_errstring(result, NULL, NULL));
+ return NOTOK;
+ }
+
+ result = sasl_setprop(conn, SASL_SSF_EXTERNAL, &extprops);
+
+ if (result != SASL_OK) {
+ sm_ierror("SASL external property initialization failed: %s",
+ sasl_errstring(result, NULL, NULL));
+ return NOTOK;
+ }
+
+ /*
+ * Start the actual protocol. Feed the mech list into the library
+ * and get out a possible initial challenge
+ */
+
+ result = sasl_client_start(conn, mechlist, NULL, NULL, &buf, &buflen,
+ &chosen_mech);
+
+ if (result != SASL_OK && result != SASL_CONTINUE) {
+ sm_ierror("SASL client start failed: %s",
+ sasl_errstring(result, NULL, NULL));
+ return NOTOK;
+ }
+
+ /*
+ * If we got an initial challenge, send it as part of the AUTH
+ * command; otherwise, just send a plain AUTH command.
+ */
+
+ if (buflen) {
+ status = sasl_encode64(buf, buflen, outbuf, sizeof(outbuf), NULL);
+ free(buf);
+ if (status != SASL_OK) {
+ sm_ierror("SASL base64 encode failed: %s",
+ sasl_errstring(status, NULL, NULL));
+ return NOTOK;
+ }
+
+ status = smtalk(SM_AUTH, "AUTH %s %s", chosen_mech, outbuf);
+ } else
+ status = smtalk(SM_AUTH, "AUTH %s", chosen_mech);
+
+ /*
+ * Now we loop until we either fail, get a SASL_OK, or a 235
+ * response code. Receive the challenges and process them until
+ * we're all done.
+ */
+
+ while (result == SASL_CONTINUE) {
+
+ /*
+ * If we get a 235 response, that means authentication has
+ * succeeded and we need to break out of the loop (yes, even if
+ * we still get SASL_CONTINUE from sasl_client_step()).
+ *
+ * Otherwise, if we get a message that doesn't seem to be a
+ * valid response, then abort
+ */
+
+ if (status == 235)
+ break;
+ else if (status < 300 || status > 399)
+ return RP_BHST;
+
+ /*
+ * Special case; a zero-length response from the SMTP server
+ * is returned as a single =. If we get that, then set buflen
+ * to be zero. Otherwise, just decode the response.
+ */
+
+ if (strcmp("=", sm_reply.text) == 0) {
+ outlen = 0;
+ } else {
+ result = sasl_decode64(sm_reply.text, sm_reply.length,
+ outbuf, &outlen);
+
+ if (result != SASL_OK) {
+ smtalk(SM_AUTH, "*");
+ sm_ierror("SASL base64 decode failed: %s",
+ sasl_errstring(result, NULL, NULL));
+ return NOTOK;
+ }
+ }
+
+ result = sasl_client_step(conn, outbuf, outlen, NULL, &buf, &buflen);
+
+ if (result != SASL_OK && result != SASL_CONTINUE) {
+ smtalk(SM_AUTH, "*");
+ sm_ierror("SASL client negotiation failed: %s",
+ sasl_errstring(result, NULL, NULL));
+ return NOTOK;
+ }
+
+ status = sasl_encode64(buf, buflen, outbuf, sizeof(outbuf), NULL);
+ free(buf);
+
+ if (status != SASL_OK) {
+ smtalk(SM_AUTH, "*");
+ sm_ierror("SASL base64 encode failed: %s",
+ sasl_errstring(status, NULL, NULL));
+ return NOTOK;
+ }
+
+ status = smtalk(SM_AUTH, outbuf);
+ }
+
+ /*
+ * Make sure that we got the correct response
+ */
+
+ if (status < 200 || status > 299)
+ return RP_BHST;
+
+ /*
+ * Depending on the mechanism, we need to do a FINAL call to
+ * sasl_client_step(). Do that now.
+ */
+
+ result = sasl_client_step(conn, NULL, 0, NULL, &buf, &buflen);
+
+ if (result != SASL_OK) {
+ sm_ierror("SASL final client negotiation failed: %s",
+ sasl_errstring(result, NULL, NULL));
+ return NOTOK;
+ }
+
+ /*
+ * We _should_ have completed the authentication successfully.
+ * Get a few properties from the authentication exchange.
+ */
+
+ result = sasl_getprop(conn, SASL_MAXOUTBUF, (void **) &outbufmax);
+
+ if (result != SASL_OK) {
+ sm_ierror("Cannot retrieve SASL negotiated output buffer size: %s",
+ sasl_errstring(result, NULL, NULL));
+ return NOTOK;
+ }
+
+ maxoutbuf = *outbufmax;
+
+ result = sasl_getprop(conn, SASL_SSF, (void **) &ssf);
+
+ sasl_ssf = *ssf;
+
+ if (result != SASL_OK) {
+ sm_ierror("Cannot retrieve SASL negotiated security strength "
+ "factor: %s", sasl_errstring(result, NULL, NULL));
+ return NOTOK;
+ }
+
+ if (maxoutbuf == 0 || maxoutbuf > BUFSIZ)
+ maxoutbuf = BUFSIZ;
+
+ sasl_complete = 1;
+
+ return RP_OK;
+}
+
+/*
+ * Our callback functions to feed data to the SASL library
+ */
+
+static int
+sm_get_user(void *context, int id, const char **result, unsigned *len)
+{
+ char *user = (char *) context;
+
+ if (! result || id != SASL_CB_USER)
+ return SASL_BADPARAM;
+
+ *result = user;
+ if (len)
+ *len = strlen(user);
+
+ return SASL_OK;
+}
+
+static int
+sm_get_pass(sasl_conn_t *conn, void *context, int id,
+ sasl_secret_t **psecret)
+{
+ char **pw_context = (char **) context;
+ char *pass = NULL;
+ int len;
+
+ if (! psecret || id != SASL_CB_PASS)
+ return SASL_BADPARAM;
+
+ ruserpass(pw_context[0], &(pw_context[1]), &pass);
+
+ len = strlen(pass);
+
+ *psecret = (sasl_secret_t *) malloc(sizeof(sasl_secret_t) + len);
+
+ if (! *psecret) {
+ free(pass);
+ return SASL_NOMEM;
+ }
+
+ (*psecret)->len = len;
+ strcpy((*psecret)->data, pass);
+/* free(pass); */
+
+ return SASL_OK;
+}
+#endif /* CYRUS_SASL */
+
static int
sm_ierror (char *fmt, ...)
{
* prototypes
*/
/* int client (); */
-int sm_init (char *, char *, int, int, int, int, int);
+int sm_init (char *, char *, int, int, int, int, int, int, char *, char *);
int sm_winit (int, char *);
int sm_wadr (char *, char *, char *);
int sm_waend (void);
# define KPOPminc(a) 0
#endif
+#ifndef CYRUS_SASL
+# define SASLminc(a) (a)
+#else
+# define SASLminc(a) 0
+#endif
+
static struct swit switches[] = {
#define AUDSW 0
{ "audit audit-file", 0 },
{ "snoop", -5 },
#define KPOPSW 23
{ "kpop", KPOPminc (-4) },
+#define SASLSW 24
+ { "sasl", SASLminc(-4) },
+#define SASLMECHSW 25
+ { "saslmech", SASLminc(-8) },
{ NULL, 0 }
};
case SNOOPSW:
snoop++;
continue;
+
+ case SASLSW:
+ sasl++;
+ continue;
+
+ case SASLMECHSW:
+ if (!(saslmech = *argp++) || *saslmech == '-')
+ adios (NULL, "missing argument to %s", argp[-2]);
+ continue;
}
}
if (*cp == '+' || *cp == '@') {
if ( strcmp( POPSERVICE, "kpop" ) == 0 ) {
kpop = 1;
}
- if (kpop || ( rpop > 0))
+ if (kpop || sasl || ( rpop > 0))
pass = getusername ();
else
ruserpass (host, &user, &pass);
/*
* initialize POP connection
*/
- if (pop_init (host, user, pass, snoop, kpop ? 1 : rpop, kpop) == NOTOK)
+ if (pop_init (host, user, pass, snoop, kpop ? 1 : rpop, kpop,
+ sasl, saslmech) == NOTOK)
adios (NULL, "%s", response);
/* Check if there are any messages */
# define KPOPminc(a) 0
#endif
+#ifndef CYRUS_SASL
+# define SASLminc(a) (a)
+#else
+# define SASLminc(a) 0
+#endif
+
static struct swit switches[] = {
#define DATESW 0
{ "date", 0 },
{ "snoop", -5 },
#define KPOPSW 13
{ "kpop", KPOPminc (-4) },
+#define SASLSW 14
+ { "sasl", SASLminc(-4) },
+#define SASLMECHSW 15
+ { "saslmech", SASLminc(-5) },
{ NULL, 0 }
};
static int checkmail (char *, char *, int, int, int);
#ifdef POP
-static int remotemail (char *, char *, int, int, int, int, int);
+static int remotemail (char *, char *, int, int, int, int, int, int, char *);
#endif
{
int datesw = 1, notifysw = NT_ALL;
int rpop, status = 0;
- int kpop = 0;
+ int kpop = 0, sasl = 0;
int snoop = 0, vecp = 0;
uid_t uid;
- char *cp, *host = NULL, *user, buf[BUFSIZ];
+ char *cp, *host = NULL, *user, buf[BUFSIZ], *saslmech = NULL;
char **argp, **arguments, *vec[MAXVEC];
struct passwd *pw;
case SNOOPSW:
snoop++;
continue;
+
+ case SASLSW:
+ sasl++;
+ continue;
+
+ case SASLMECHSW:
+ if (!(saslmech = *argp++) || *saslmech == '-')
+ adios (NULL, "missing argument to %s", argp[-2]);
+ continue;
}
}
if (vecp >= MAXVEC-1)
kpop = 1;
}
if (vecp == 0) {
- status = remotemail (host, user, rpop, kpop, notifysw, 1, snoop);
+ status = remotemail (host, user, rpop, kpop, notifysw, 1, snoop,
+ sasl, saslmech);
} else {
for (vecp = 0; vec[vecp]; vecp++)
- status += remotemail (host, vec[vecp], rpop, kpop, notifysw, 0, snoop);
+ status += remotemail (host, vec[vecp], rpop, kpop, notifysw, 0,
+ snoop, sasl, saslmech);
}
} else {
#endif /* POP */
extern char response[];
static int
-remotemail (char *host, char *user, int rpop, int kpop, int notifysw, int personal, int snoop)
+remotemail (char *host, char *user, int rpop, int kpop, int notifysw, int personal, int snoop, int sasl, char *saslmech)
{
int nmsgs, nbytes, status;
char *pass = NULL;
if (user == NULL)
user = getusername ();
- if (kpop || (rpop > 0))
+ if (kpop || sasl || (rpop > 0))
pass = getusername ();
else
ruserpass (host, &user, &pass);
/* open the POP connection */
- if (pop_init (host, user, pass, snoop, kpop ? 1 : rpop, kpop) == NOTOK
+ if (pop_init (host, user, pass, snoop, kpop ? 1 : rpop, kpop,
+ sasl, saslmech) == NOTOK
|| pop_stat (&nmsgs, &nbytes) == NOTOK /* check for messages */
|| pop_quit () == NOTOK) { /* quit POP connection */
advise (NULL, "%s", response);
-
/*
* popsbr.c -- POP client subroutines
*
# include <h/md5.h>
#endif
+#ifdef CYRUS_SASL
+# include <sasl.h>
+#endif /* CYRUS_SASL */
+
#include <h/popsbr.h>
#include <h/signals.h>
#include <signal.h>
# endif
#endif /* NNTP */
+#ifdef CYRUS_SASL
+static sasl_conn_t *conn; /* SASL connection state */
+static int sasl_complete = 0; /* Has sasl authentication succeeded? */
+static int maxoutbuf; /* Maximum output buffer size */
+static sasl_ssf_t sasl_ssf = 0; /* Security strength factor */
+static int sasl_get_user(void *, int, const char **, unsigned *);
+static int sasl_get_pass(sasl_conn_t *, void *, int, sasl_secret_t **);
+struct pass_context {
+ char *user;
+ char *host;
+};
+
+static sasl_callback_t callbacks[] = {
+ { SASL_CB_USER, sasl_get_user, NULL },
+#define POP_SASL_CB_N_USER 0
+ { SASL_CB_PASS, sasl_get_pass, NULL },
+#define POP_SASL_CB_N_PASS 1
+ { SASL_CB_LIST_END, NULL, NULL },
+};
+#else /* CYRUS_SASL */
+# define sasl_fgetc fgetc
+#endif /* CYRUS_SASL */
+
/*
* static prototypes
*/
static int multiline(void);
#endif
+#ifdef CYRUS_SASL
+static int pop_auth_sasl(char *, char *, char *);
+static int sasl_fgetc(FILE *);
+#endif /* CYRUS_SASL */
+
static int traverse (int (*)(), const char *, ...);
static int vcommand(const char *, va_list);
static int getline (char *, int, FILE *);
}
#endif /* !NNTP && APOP */
+#ifdef CYRUS_SASL
+/*
+ * This function implements the AUTH command for various SASL mechanisms
+ *
+ * We do the whole SASL dialog here. If this completes, then we've
+ * authenticated successfully and have (possibly) negotiated a security
+ * layer.
+ */
+
+int
+pop_auth_sasl(char *user, char *host, char *mech)
+{
+ int result, status, sasl_capability = 0, outlen;
+ unsigned int buflen;
+ char server_mechs[256], *buf, outbuf[BUFSIZ];
+ const char *chosen_mech;
+ sasl_security_properties_t secprops;
+ sasl_external_properties_t extprops;
+ struct pass_context p_context;
+ sasl_ssf_t *ssf;
+ int *moutbuf;
+
+ /*
+ * First off, we're going to send the CAPA command to see if we can
+ * even support the AUTH command, and if we do, then we'll get a
+ * list of mechanisms the server supports. If we don't support
+ * the CAPA command, then it's unlikely that we will support
+ * SASL
+ */
+
+ if (command("CAPA") == NOTOK) {
+ snprintf(response, sizeof(response),
+ "The POP CAPA command failed; POP server does not "
+ "support SASL");
+ return NOTOK;
+ }
+
+ while ((status = multiline()) != DONE)
+ switch (status) {
+ case NOTOK:
+ return NOTOK;
+ break;
+ case DONE: /* Shouldn't be possible, but just in case */
+ break;
+ case OK:
+ if (strncasecmp(response, "SASL ", 5) == 0) {
+ /*
+ * We've seen the SASL capability. Grab the mech list
+ */
+ sasl_capability++;
+ strncpy(server_mechs, response + 5, sizeof(server_mechs));
+ }
+ break;
+ }
+
+ if (!sasl_capability) {
+ snprintf(response, sizeof(response), "POP server does not support "
+ "SASL");
+ return NOTOK;
+ }
+
+ /*
+ * If we received a preferred mechanism, see if the server supports it.
+ */
+
+ if (mech && stringdex(mech, server_mechs) == -1) {
+ snprintf(response, sizeof(response), "Requested SASL mech \"%s\" is "
+ "not in list of supported mechanisms:\n%s",
+ mech, server_mechs);
+ return NOTOK;
+ }
+
+ /*
+ * Start the SASL process. First off, initialize the SASL library.
+ */
+
+ callbacks[POP_SASL_CB_N_USER].context = user;
+ p_context.user = user;
+ p_context.host = host;
+ callbacks[POP_SASL_CB_N_PASS].context = &p_context;
+
+ result = sasl_client_init(callbacks);
+
+ if (result != SASL_OK) {
+ snprintf(response, sizeof(response), "SASL library initialization "
+ "failed: %s", sasl_errstring(result, NULL, NULL));
+ return NOTOK;
+ }
+
+ result = sasl_client_new("pop", host, NULL, SASL_SECURITY_LAYER, &conn);
+
+ if (result != SASL_OK) {
+ snprintf(response, sizeof(response), "SASL client initialization "
+ "failed: %s", sasl_errstring(result, NULL, NULL));
+ return NOTOK;
+ }
+
+ /*
+ * Initialize the security properties
+ */
+
+ memset(&secprops, 0, sizeof(secprops));
+ secprops.maxbufsize = BUFSIZ;
+ secprops.max_ssf = UINT_MAX;
+ memset(&extprops, 0, sizeof(extprops));
+
+ result = sasl_setprop(conn, SASL_SEC_PROPS, &secprops);
+
+ if (result != SASL_OK) {
+ snprintf(response, sizeof(response), "SASL security property "
+ "initialization failed: %s",
+ sasl_errstring(result, NULL, NULL));
+ return NOTOK;
+ }
+
+ result = sasl_setprop(conn, SASL_SSF_EXTERNAL, &extprops);
+
+ if (result != SASL_OK) {
+ snprintf(response, sizeof(response), "SASL external property "
+ "initialization failed: %s",
+ sasl_errstring(result, NULL, NULL));
+ return NOTOK;
+ }
+
+ /*
+ * Start the actual protocol. Feed the mech list into the library
+ * and get out a possible initial challenge
+ */
+
+ result = sasl_client_start(conn, mech ? mech : server_mechs,
+ NULL, NULL, &buf, &buflen, &chosen_mech);
+
+ if (result != SASL_OK && result != SASL_CONTINUE) {
+ snprintf(response, sizeof(response), "SASL client start failed: %s",
+ sasl_errstring(result, NULL, NULL));
+ return NOTOK;
+ }
+
+ if (buflen) {
+ status = sasl_encode64(buf, buflen, outbuf, sizeof(outbuf), NULL);
+ free(buf);
+ if (status != SASL_OK) {
+ snprintf(response, sizeof(response), "SASL base64 encode "
+ "failed: %s", sasl_errstring(status, NULL, NULL));
+ return NOTOK;
+ }
+
+ status = command("AUTH %s %s", chosen_mech, outbuf);
+ } else
+ status = command("AUTH %s", chosen_mech);
+
+ while (result == SASL_CONTINUE) {
+ if (status == NOTOK)
+ return NOTOK;
+
+ /*
+ * If we get a "+OK" prefix to our response, then we should
+ * exit out of this exchange now (because authenticated should
+ * have succeeded)
+ */
+
+ if (strncmp(response, "+OK", 3) == 0)
+ break;
+
+ /*
+ * Otherwise, make sure the server challenge is correctly formatted
+ */
+
+ if (strncmp(response, "+ ", 2) != 0) {
+ command("*");
+ snprintf(response, sizeof(response),
+ "Malformed authentication message from server");
+ return NOTOK;
+ }
+
+ result = sasl_decode64(response + 2, strlen(response + 2),
+ outbuf, &outlen);
+
+ if (result != SASL_OK) {
+ command("*");
+ snprintf(response, sizeof(response), "SASL base64 decode "
+ "failed: %s", sasl_errstring(result, NULL, NULL));
+ return NOTOK;
+ }
+
+ result = sasl_client_step(conn, outbuf, outlen, NULL, &buf, &buflen);
+
+ if (result != SASL_OK && result != SASL_CONTINUE) {
+ command("*");
+ snprintf(response, sizeof(response), "SASL client negotiaton "
+ "failed: %s", sasl_errstring(result, NULL, NULL));
+ return NOTOK;
+ }
+
+ status = sasl_encode64(buf, buflen, outbuf, sizeof(outbuf), NULL);
+ free(buf);
+
+ if (status != SASL_OK) {
+ command("*");
+ snprintf(response, sizeof(response), "SASL base64 encode "
+ "failed: %s", sasl_errstring(status, NULL, NULL));
+ return NOTOK;
+ }
+
+ status = command(outbuf);
+ }
+
+ /*
+ * If we didn't get a positive final response, then error out
+ * (that probably means we failed an authorization check).
+ */
+
+ if (status != OK)
+ return NOTOK;
+
+ /*
+ * Depending on the mechanism, we might need to call sasl_client_step()
+ * one more time. Do that now.
+ */
+
+ result = sasl_client_step(conn, NULL, 0, NULL, &buf, &buflen);
+
+ if (result != SASL_OK) {
+ snprintf(response, sizeof(response), "SASL final client negotiaton "
+ "failed: %s", sasl_errstring(result, NULL, NULL));
+ return NOTOK;
+ }
+
+ /*
+ * We _should_ be okay now. Get a few properties now that negotiation
+ * has completed.
+ */
+
+ result = sasl_getprop(conn, SASL_MAXOUTBUF, (void **) &moutbuf);
+
+ if (result != SASL_OK) {
+ snprintf(response, sizeof(response), "Cannot retrieve SASL negotiated "
+ "output buffer size: %s", sasl_errstring(result, NULL, NULL));
+ return NOTOK;
+ }
+
+ maxoutbuf = *moutbuf;
+
+ result = sasl_getprop(conn, SASL_SSF, (void **) &ssf);
+
+ sasl_ssf = *ssf;
+
+ if (result != SASL_OK) {
+ snprintf(response, sizeof(response), "Cannot retrieve SASL negotiated "
+ "security strength factor: %s",
+ sasl_errstring(result, NULL, NULL));
+ return NOTOK;
+ }
+
+ /*
+ * Limit this to what we can deal with.
+ */
+
+ if (maxoutbuf == 0 || maxoutbuf > BUFSIZ)
+ maxoutbuf = BUFSIZ;
+
+ sasl_complete = 1;
+
+ return status;
+}
+
+/*
+ * Callback to return the userid sent down via the user parameter
+ */
+
+static int
+sasl_get_user(void *context, int id, const char **result, unsigned *len)
+{
+ char *user = (char *) context;
+
+ if (! result || id != SASL_CB_USER)
+ return SASL_BADPARAM;
+
+ *result = user;
+ if (len)
+ *len = strlen(user);
+
+ return SASL_OK;
+}
+
+/*
+ * Callback to return the password (we call ruserpass, which can get it
+ * out of the .netrc
+ */
+
+static int
+sasl_get_pass(sasl_conn_t *conn, void *context, int id, sasl_secret_t **psecret)
+{
+ struct pass_context *p_context = (struct pass_context *) context;
+ char *pass = NULL;
+ int len;
+
+ if (! psecret || id != SASL_CB_PASS)
+ return SASL_BADPARAM;
+
+ ruserpass(p_context->user, &(p_context->host), &pass);
+
+ len = strlen(pass);
+
+ *psecret = (sasl_secret_t *) malloc(sizeof(sasl_secret_t) + len);
+
+ if (! *psecret)
+ return SASL_NOMEM;
+
+ (*psecret)->len = len;
+ strcpy((*psecret)->data, pass);
+
+ return SASL_OK;
+}
+#endif /* CYRUS_SASL */
int
-pop_init (char *host, char *user, char *pass, int snoop, int rpop, int kpop)
+pop_init (char *host, char *user, char *pass, int snoop, int rpop, int kpop,
+ int sasl, char *mech)
{
int fd1, fd2;
char buffer[BUFSIZ];
}
else
# endif /* APOP */
+# ifdef CYRUS_SASL
+ if (sasl) {
+ if (pop_auth_sasl(user, host, mech) != NOTOK)
+ return OK;
+ } else
+# endif /* CYRUS_SASL */
if (command ("USER %s", user) != NOTOK
&& command ("%s %s", rpop ? "RPOP" : (pophack++, "PASS"),
pass) != NOTOK)
int
pop_done (void)
{
+#ifdef CYRUS_SASL
+ if (conn)
+ sasl_dispose(&conn);
+#endif /* CYRUS_SASL */
fclose (input);
fclose (output);
vsnprintf (buffer, sizeof(buffer), fmt, ap);
if (poprint) {
+#ifdef CYRUS_SASL
+ if (sasl_ssf)
+ fprintf(stderr, "(encrypted) ");
+#endif /* CYRUS_SASL */
if (pophack) {
if ((cp = strchr (buffer, ' ')))
*cp = 0;
if (putline (buffer, output) == NOTOK)
return NOTOK;
+#ifdef CYRUS_SASL
+ if (poprint && sasl_ssf)
+ fprintf(stderr, "(decrypted) ");
+#endif /* CYRUS_SASL */
+
switch (getline (response, sizeof response, input)) {
case OK:
if (poprint)
if (getline (buffer, sizeof buffer, input) != OK)
return NOTOK;
#ifdef DEBUG
- if (poprint)
+ if (poprint) {
+#ifdef CYRUS_SASL
+ if (sasl_ssf)
+ fprintf(stderr, "(decrypted) ");
+#endif /* CYRUS_SASL */
fprintf (stderr, "<--- %s\n", response);
+ }
#endif DEBUG
if (strncmp (buffer, TRM, TRMLEN) == 0) {
if (buffer[TRMLEN] == 0)
return OK;
}
+/*
+ * Note that these functions have been modified to deal with layer encryption
+ * in the SASL case
+ */
static int
getline (char *s, int n, FILE *iop)
char *p;
p = s;
- while (--n > 0 && (c = fgetc (iop)) != EOF)
+ while (--n > 0 && (c = sasl_fgetc (iop)) != EOF && c != -2)
if ((*p++ = c) == '\n')
break;
+ if (c == -2)
+ return NOTOK;
if (ferror (iop) && c != EOF) {
strncpy (response, "error on connection", sizeof(response));
return NOTOK;
static int
putline (char *s, FILE *iop)
{
- fprintf (iop, "%s\r\n", s);
+#ifdef CYRUS_SASL
+ char outbuf[BUFSIZ], *buf;
+ int result;
+ unsigned int buflen;
+
+ if (!sasl_complete) {
+#endif /* CYRUS_SASL */
+ fprintf (iop, "%s\r\n", s);
+#ifdef CYRUS_SASL
+ } else {
+ /*
+ * Build an output buffer, encrypt it using sasl_encode, and
+ * squirt out the results.
+ */
+ strncpy(outbuf, s, sizeof(outbuf) - 3);
+ outbuf[sizeof(outbuf) - 3] = '\0'; /* Just in case */
+ strcat(outbuf, "\r\n");
+
+ result = sasl_encode(conn, outbuf, strlen(outbuf), &buf, &buflen);
+
+ if (result != SASL_OK) {
+ snprintf(response, sizeof(response), "SASL encoding error: %s",
+ sasl_errstring(result, NULL, NULL));
+ return NOTOK;
+ }
+
+ fwrite(buf, buflen, 1, iop);
+ free(buf);
+ }
+#endif /* CYRUS_SASL */
+
fflush (iop);
if (ferror (iop)) {
strncpy (response, "lost connection", sizeof(response));
return OK;
}
+
+#ifdef CYRUS_SASL
+/*
+ * Okay, our little fgetc replacement. Hopefully this is a little more
+ * efficient than the last one.
+ */
+static int
+sasl_fgetc(FILE *f)
+{
+ static unsigned char *buffer = NULL, *ptr;
+ static int size = 0;
+ static int cnt = 0;
+ unsigned int retbufsize = 0;
+ int cc, result;
+ char *retbuf, tmpbuf[BUFSIZ];
+
+ /*
+ * If we have some leftover data, return that
+ */
+
+ if (cnt) {
+ cnt--;
+ return (int) *ptr++;
+ }
+
+ /*
+ * Otherwise, fill our buffer until we have some data to return.
+ */
+
+ while (retbufsize == 0) {
+
+ cc = read(fileno(f), tmpbuf, sizeof(tmpbuf));
+
+ if (cc == 0)
+ return EOF;
+
+ if (cc < 0) {
+ snprintf(response, sizeof(response), "Error during read from "
+ "network: %s", strerror(errno));
+ return -2;
+ }
+
+ /*
+ * We're not allowed to call sasl_decode until sasl_complete is
+ * true, so we do these gyrations ...
+ */
+
+ if (!sasl_complete) {
+
+ retbuf = tmpbuf;
+ retbufsize = cc;
+
+ } else {
+
+ result = sasl_decode(conn, tmpbuf, cc, &retbuf, &retbufsize);
+
+ if (result != SASL_OK) {
+ snprintf(response, sizeof(response), "Error during SASL "
+ "decoding: %s", sasl_errstring(result, NULL, NULL));
+ return -2;
+ }
+ }
+ }
+
+ if (retbufsize > size) {
+ buffer = realloc(buffer, retbufsize);
+ if (!buffer) {
+ snprintf(response, sizeof(response), "Error during realloc in "
+ "read routine: %s", strerror(errno));
+ return -2;
+ }
+ size = retbufsize;
+ }
+
+ memcpy(buffer, retbuf, retbufsize);
+ ptr = buffer + 1;
+ cnt = retbufsize - 1;
+ if (sasl_complete)
+ free(retbuf);
+
+ return (int) buffer[0];
+}
+#endif /* CYRUS_SASL */
# define uptolow(c) ((isalpha(c) && isupper (c)) ? tolower (c) : (c))
#endif
+#ifndef CYRUS_SASL
+# define SASLminc(a) (a)
+#else /* CYRUS_SASL */
+# define SASLminc(a) 0
+#endif /* CYRUS_SASL */
+
#define FCCS 10 /* max number of fccs allowed */
/* In the following array of structures, the numeric second field of the
{ "partno", -6 },
#define QUEUESW 36
{ "queued", -6 },
+#define SASLSW 37
+ { "sasl", SASLminc(-4) },
+#define SASLMECHSW 38
+ { "saslmech", SASLminc(-5) },
+#define USERSW 39
+ { "user", SASLminc(-4) },
{ NULL, 0 }
};
static int checksw = 0; /* whom -check */
static int linepos=0; /* putadr()'s position on the line */
static int nameoutput=0; /* putadr() has output header name */
+static int sasl=0; /* Use SASL auth for SMTP */
+static char *saslmech=NULL; /* Force use of particular SASL mech */
+static char *user=NULL; /* Authenticate as this user */
static unsigned msgflags = 0; /* what we've seen */
case QUEUESW:
queued++;
continue;
+
+ case SASLSW:
+ sasl++;
+ continue;
+
+ case SASLMECHSW:
+ if (!(saslmech = *argp++) || *saslmech == '-')
+ adios (NULL, "missing argument to %s", argp[-2]);
+ continue;
+
+ case USERSW:
+ if (!(user = *argp++) || *user == '-')
+ adios (NULL, "missing argument to %s", argp[-2]);
+ continue;
}
}
if (msg)
sigon ();
if (rp_isbad (retval = sm_init (clientsw, serversw, watch, verbose,
- snoop, onex, queued))
+ snoop, onex, queued, sasl, saslmech,
+ user))
|| rp_isbad (retval = sm_winit (smtpmode, from)))
die (NULL, "problem initializing server; %s", rp_string (retval));
sigon ();
if (!whomsw || checksw)
- if (rp_isbad (retval = sm_init (clientsw, serversw, 0, 0, snoop, 0, 0))
+ if (rp_isbad (retval = sm_init (clientsw, serversw, 0, 0, snoop, 0,
+ 0, 0, 0, 0))
|| rp_isbad (retval = sm_winit (smtpmode, from)))
die (NULL, "problem initializing server; %s", rp_string (retval));
#include <signal.h>
+#ifndef CYRUS_SASL
+# define SASLminc(a) (a)
+#else /* CYRUS_SASL */
+# define SASLminc(a) 0
+#endif /* CYRUS_SASL */
+
static struct swit switches[] = {
#define ALIASW 0
{ "alias aliasfile", 0 },
{ "server host", -6 },
#define SNOOPSW 36
{ "snoop", -5 },
+#define SASLSW 37
+ { "sasl", SASLminc(-4) },
+#define SASLMECHSW 38
+ { "saslmech", SASLminc(-5) },
+#define USERSW 39
+ { "user", SASLminc(-4) },
{ NULL, 0 }
};
case SENDSW:
case SOMLSW:
case SNOOPSW:
+ case SASLSW:
vec[vecp++] = --cp;
continue;
case WIDTHSW:
case CLIESW:
case SERVSW:
+ case SASLMECHSW:
+ case USERSW:
vec[vecp++] = --cp;
if (!(cp = *argp++) || *cp == '-')
adios (NULL, "missing argument to %s", argp[-2]);
}
+#ifndef CYRUS_SASL
+# define SASLminc(a) (a)
+#else /* CYRUS_SASL */
+# define SASLminc(a) 0
+#endif /* CYRUS_SASL */
+
static struct swit sendswitches[] = {
#define ALIASW 0
{ "alias aliasfile", 0 },
{ "draftmessage msg", -6 },
#define SNDRFSW 35
{ "nodraftfolder", -3 },
+#define SASLSW 36
+ { "sasl", SASLminc(-4) },
+#define SASLMECHSW 37
+ { "saslmech", SASLminc(-5) },
+#define USERSW 38
+ { "user", SASLminc(-4) },
{ NULL, 0 }
};
case SSNDSW:
case SOMLSW:
case SNOOPSW:
+ case SASLSW:
vec[vecp++] = --cp;
continue;
case WIDTHSW:
case CLIESW:
case SERVSW:
+ case SASLMECHSW:
+ case USERSW:
vec[vecp++] = --cp;
if (!(cp = *argp++) || *cp == '-') {
advise (NULL, "missing argument to %s", argp[-2]);