-
/*
* popsbr.c -- POP client subroutines
*
* $Id$
+ *
+ * This code is Copyright (c) 2002, by the authors of nmh. See the
+ * COPYRIGHT file in the root directory of the nmh distribution for
+ * complete copyright information.
*/
#include <h/mh.h>
+#include <h/utils.h>
extern int client(char *args, char *protocol, char *service, int rproto,
char *response, int len_response);
# include <h/md5.h>
#endif
+#ifdef CYRUS_SASL
+# include <sasl/sasl.h>
+# include <sasl/saslutil.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_LOG, NULL, NULL },
+ { SASL_CB_LIST_END, NULL, NULL },
+};
+#else /* CYRUS_SASL */
+# define sasl_fgetc fgetc
+#endif /* CYRUS_SASL */
+
/*
* static prototypes
*/
static int multiline(void);
#endif
-static int traverse (int (*)(), const char *, ...);
+#ifdef CYRUS_SASL
+static int pop_auth_sasl(char *, char *, char *);
+static int sasl_fgetc(FILE *);
+#endif /* CYRUS_SASL */
+
+static int traverse (int (*)(char *), const char *, ...);
static int vcommand(const char *, va_list);
-static int getline (char *, int, FILE *);
+static int sasl_getline (char *, int, FILE *);
static int putline (char *, FILE *);
return NULL;
}
- *++lp = NULL;
+ *(++lp) = '\0';
snprintf (buffer, sizeof(buffer), "%s%s", cp, pass);
MD5Init (&mdContext);
cp += 2;
buflen -= 2;
}
- *cp = NULL;
+ *cp = '\0';
return buffer;
}
#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;
+ unsigned int buflen, outlen;
+ char server_mechs[256], *buf, outbuf[BUFSIZ];
+ const char *chosen_mech;
+ sasl_security_properties_t secprops;
+ 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, NULL, NULL, 0, &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;
+
+ result = sasl_setprop(conn, SASL_SEC_PROPS, &secprops);
+
+ if (result != SASL_OK) {
+ snprintf(response, sizeof(response), "SASL security property "
+ "initialization failed: %s", sasl_errdetail(conn));
+ 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,
+ (const char *) (mech ? mech : server_mechs),
+ NULL, (const char **) &buf,
+ &buflen, &chosen_mech);
+
+ if (result != SASL_OK && result != SASL_CONTINUE) {
+ snprintf(response, sizeof(response), "SASL client start failed: %s",
+ sasl_errdetail(conn));
+ return NOTOK;
+ }
+
+ if (buflen) {
+ status = sasl_encode64(buf, buflen, outbuf, sizeof(outbuf), NULL);
+ 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, sizeof(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,
+ (const char **) &buf, &buflen);
+
+ if (result != SASL_OK && result != SASL_CONTINUE) {
+ command("*");
+ snprintf(response, sizeof(response), "SASL client negotiaton "
+ "failed: %s", sasl_errdetail(conn));
+ return NOTOK;
+ }
+
+ status = sasl_encode64(buf, buflen, outbuf, sizeof(outbuf), NULL);
+
+ 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;
+
+ /*
+ * We _should_ be okay now. Get a few properties now that negotiation
+ * has completed.
+ */
+
+ result = sasl_getprop(conn, SASL_MAXOUTBUF, (const void **) &moutbuf);
+
+ if (result != SASL_OK) {
+ snprintf(response, sizeof(response), "Cannot retrieve SASL negotiated "
+ "output buffer size: %s", sasl_errdetail(conn));
+ return NOTOK;
+ }
+
+ maxoutbuf = *moutbuf;
+
+ result = sasl_getprop(conn, SASL_SSF, (const void **) &ssf);
+
+ sasl_ssf = *ssf;
+
+ if (result != SASL_OK) {
+ snprintf(response, sizeof(response), "Cannot retrieve SASL negotiated "
+ "security strength factor: %s", sasl_errdetail(conn));
+ 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 *) mh_xmalloc(sizeof(sasl_secret_t) + len);
+
+ (*psecret)->len = len;
+ strcpy((char *) (*psecret)->data, pass);
+
+ return SASL_OK;
+}
+#endif /* CYRUS_SASL */
+
+
+/*
+ * Split string containing proxy command into an array of arguments
+ * suitable for passing to exec. Returned array must be freed. Shouldn't
+ * be possible to call this with host set to NULL.
+ */
+char **
+parse_proxy(char *proxy, char *host)
+{
+ char **pargv, **p;
+ int pargc = 2;
+ int hlen = strlen(host);
+ int plen = 1;
+ unsigned char *cur, *pro;
+ char *c;
+
+ /* skip any initial space */
+ for (pro = proxy; isspace(*pro); pro++)
+ continue;
+
+ /* calculate required size for argument array */
+ for (cur = pro; *cur; cur++) {
+ if (isspace(*cur) && cur[1] && !isspace(cur[1]))
+ plen++, pargc++;
+ else if (*cur == '%' && cur[1] == 'h') {
+ plen += hlen;
+ cur++;
+ } else if (!isspace(*cur))
+ plen++;
+ }
+
+ /* put together list of arguments */
+ p = pargv = mh_xmalloc(pargc * sizeof(char *));
+ c = *pargv = mh_xmalloc(plen * sizeof(char));
+ for (cur = pro; *cur; cur++) {
+ if (isspace(*cur) && cur[1] && !isspace(cur[1])) {
+ *c++ = '\0';
+ *++p = c;
+ } else if (*cur == '%' && cur[1] == 'h') {
+ strcpy (c, host);
+ c += hlen;
+ cur++;
+ } else if (!isspace(*cur))
+ *c++ = *cur;
+ }
+ *++p = NULL;
+ return pargv;
+}
int
-pop_init (char *host, char *user, char *pass, int snoop, int rpop, int kpop)
+pop_init (char *host, char *user, char *pass, char *proxy, int snoop,
+ int rpop, int kpop, int sasl, char *mech)
{
int fd1, fd2;
char buffer[BUFSIZ];
rpop = 0;
#endif
+ if (proxy && *proxy) {
+ int pid;
+ int inpipe[2]; /* for reading from the server */
+ int outpipe[2]; /* for sending to the server */
+
+ /* first give up any root priviledges we may have for rpop */
+ setuid(getuid());
+
+ pipe(inpipe);
+ pipe(outpipe);
+
+ pid=fork();
+ if (pid==0) {
+ char **argv;
+
+ /* in child */
+ close(0);
+ close(1);
+ dup2(outpipe[0],0); /* connect read end of connection */
+ dup2(inpipe[1], 1); /* connect write end of connection */
+ if(inpipe[0]>1) close(inpipe[0]);
+ if(inpipe[1]>1) close(inpipe[1]);
+ if(outpipe[0]>1) close(outpipe[0]);
+ if(outpipe[1]>1) close(outpipe[1]);
+
+ /* run the proxy command */
+ argv=parse_proxy(proxy, host);
+ execvp(argv[0],argv);
+
+ perror(argv[0]);
+ close(0);
+ close(1);
+ free(*argv);
+ free(argv);
+ exit(10);
+ }
+
+ /* okay in the parent we do some stuff */
+ close(inpipe[1]); /* child uses this */
+ close(outpipe[0]); /* child uses this */
+
+ /* we read on fd1 */
+ fd1=inpipe[0];
+
+ /* and write on fd2 */
+ fd2=outpipe[1];
+
+ } else {
+
#ifndef NNTP
+ if ( kpop ) {
# ifdef KPOP
- if ( kpop ) {
- snprintf (buffer, sizeof(buffer), "%s/%s", KPOP_PRINCIPAL, "kpop");
- if ((fd1 = client (host, "tcp", buffer, 0, response, sizeof(response))) == NOTOK) {
- return NOTOK;
- }
- } else {
-# endif /* KPOP */
- if ((fd1 = client (host, "tcp", POPSERVICE, rpop, response, sizeof(response))) == NOTOK)
+ snprintf (buffer, sizeof(buffer), "%s/%s", KPOP_PRINCIPAL, "kpop");
+ if ((fd1 = client (host, "tcp", buffer, 0, response, sizeof(response))) == NOTOK) {
+ return NOTOK;
+ }
+# else /* KPOP */
+ snprintf (response, sizeof(response), "this version of nmh compiled without KPOP support");
return NOTOK;
-# ifdef KPOP
- }
# endif /* KPOP */
+ } else {
+ if ((fd1 = client (host, "tcp", POPSERVICE, rpop, response, sizeof(response))) == NOTOK) {
+ return NOTOK;
+ }
+ }
#else /* NNTP */
- if ((fd1 = client (host, "tcp", "nntp", rpop, response, sizeof(response))) == NOTOK)
- return NOTOK;
+ if ((fd1 = client (host, "tcp", "nntp", rpop, response, sizeof(response))) == NOTOK)
+ return NOTOK;
#endif
- if ((fd2 = dup (fd1)) == NOTOK) {
- char *s;
+ if ((fd2 = dup (fd1)) == NOTOK) {
+ char *s;
- if ((s = strerror(errno)))
- snprintf (response, sizeof(response),
- "unable to dup connection descriptor: %s", s);
- else
- snprintf (response, sizeof(response),
- "unable to dup connection descriptor: unknown error");
- close (fd1);
- return NOTOK;
+ if ((s = strerror(errno)))
+ snprintf (response, sizeof(response),
+ "unable to dup connection descriptor: %s", s);
+ else
+ snprintf (response, sizeof(response),
+ "unable to dup connection descriptor: unknown error");
+ close (fd1);
+ return NOTOK;
+ }
}
#ifndef NNTP
if (pop_set (fd1, fd2, snoop) == NOTOK)
SIGNAL (SIGPIPE, SIG_IGN);
- switch (getline (response, sizeof response, input)) {
+ switch (sasl_getline (response, sizeof response, input)) {
case OK:
if (poprint)
fprintf (stderr, "<--- %s\n", response);
}
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_retr (int msgno, int (*action)())
+pop_retr (int msgno, int (*action)(char *))
{
#ifndef NNTP
return traverse (action, "RETR %d", (targ_t) msgno);
static int
-traverse (int (*action)(), const char *fmt, ...)
+traverse (int (*action)(char *), const char *fmt, ...)
{
int result;
va_list ap;
int
-pop_top (int msgno, int lines, int (*action)())
+pop_top (int msgno, int lines, int (*action)(char *))
{
#ifndef NNTP
return traverse (action, "TOP %d %d", (targ_t) msgno, (targ_t) lines);
snprintf (buffer, sizeof(buffer), fmt, a, b, c, d);
ap = brkstring (buffer, " ", "\n"); /* a hack, i know... */
- if (!strcasecmp(ap[0], "x-bboards")) { /* XTND "X-BBOARDS group */
+ if (!mh_strcasecmp(ap[0], "x-bboards")) { /* XTND "X-BBOARDS group */
/* most of these parameters are meaningless under NNTP.
* bbc.c was modified to set AKA and LEADERS as appropriate,
* the rest are left blank.
*/
return OK;
}
- if (!strcasecmp (ap[0], "archive") && ap[1]) {
+ if (!mh_strcasecmp (ap[0], "archive") && ap[1]) {
snprintf (xtnd_name, sizeof(xtnd_name), "%s", ap[1]); /* save the name */
xtnd_last = 0;
xtnd_first = 1; /* setup to fail in pop_stat */
return OK;
}
- if (!strcasecmp (ap[0], "bboards")) {
+ if (!mh_strcasecmp (ap[0], "bboards")) {
if (ap[1]) { /* XTND "BBOARDS group" */
snprintf (xtnd_name, sizeof(xtnd_name), "%s", ap[1]); /* save the name */
return NOTOK; /* unknown XTND command */
#endif /* NNTP */
}
-#endif BPOP
+#endif /* BPOP */
int
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;
- switch (getline (response, sizeof response, input)) {
+#ifdef CYRUS_SASL
+ if (poprint && sasl_ssf)
+ fprintf(stderr, "(decrypted) ");
+#endif /* CYRUS_SASL */
+
+ switch (sasl_getline (response, sizeof response, input)) {
case OK:
if (poprint)
fprintf (stderr, "<--- %s\n", response);
{
char buffer[BUFSIZ + TRMLEN];
- if (getline (buffer, sizeof buffer, input) != OK)
+ if (sasl_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
+ }
+#endif /* DEBUG */
if (strncmp (buffer, TRM, TRMLEN) == 0) {
if (buffer[TRMLEN] == 0)
return DONE;
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)
+sasl_getline (char *s, int n, FILE *iop)
{
int c;
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),
+ (const char **) &buf, &buflen);
+
+ if (result != SASL_OK) {
+ snprintf(response, sizeof(response), "SASL encoding error: %s",
+ sasl_errdetail(conn));
+ return NOTOK;
+ }
+
+ fwrite(buf, buflen, 1, iop);
+ }
+#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,
+ (const char **) &retbuf, &retbufsize);
+
+ if (result != SASL_OK) {
+ snprintf(response, sizeof(response), "Error during SASL "
+ "decoding: %s", sasl_errdetail(conn));
+ return -2;
+ }
+ }
+ }
+
+ if (retbufsize > size) {
+ buffer = mh_xrealloc(buffer, retbufsize);
+ size = retbufsize;
+ }
+
+ memcpy(buffer, retbuf, retbufsize);
+ ptr = buffer + 1;
+ cnt = retbufsize - 1;
+
+ return (int) buffer[0];
+}
+#endif /* CYRUS_SASL */