]> diplodocus.org Git - nmh/commitdiff
Merge remote-tracking branch 'origin' into smtputf8
authorDavid Levine <levinedl@acm.org>
Sun, 25 Sep 2016 13:28:05 +0000 (09:28 -0400)
committerDavid Levine <levinedl@acm.org>
Sun, 25 Sep 2016 13:28:05 +0000 (09:28 -0400)
32 files changed:
Makefile.am
configure.ac
docs/contrib/localpostproc [new file with mode: 0755]
docs/contrib/replaliases
docs/pending-release-notes
h/netsec.h [new file with mode: 0644]
h/oauth.h
h/popsbr.h
h/prototypes.h
man/inc.man
man/post.man
man/send.man
man/whom.man
mts/smtp/smtp.c
mts/smtp/smtp.h
sbr/base64.c
sbr/netsec.c [new file with mode: 0644]
sbr/oauth.c
test/common.sh.in
test/oauth/common.sh
test/oauth/test-inc
test/oauth/test-send
test/oauth/test-sendfrom
test/oauth/test-share
test/send/test-sendfrom
uip/inc.c
uip/mhparse.c
uip/msgchk.c
uip/popsbr.c
uip/post.c
uip/send.c
uip/whatnowsbr.c

index ca46a4c..cb1c39f 100644 (file)
@@ -118,8 +118,8 @@ check_PROGRAMS = test/getfullname test/getcanon test/fakepop test/fakesmtp \
 ## distcheck, $nmhetcdir.  For distcheck, prepend $MH_INST_DIR (from
 ## test/common.sh.in), which is based on $MH_TEST_DIR (from
 ## $TESTS_ENVIRONMENT).
-DISTCHECK_CONFIGURE_FLAGS = DISABLE_SETGID_MAIL=1 \
-                           NMHETCDIRINST='$${abs_builddir}/test/testdir/inst'
+AM_DISTCHECK_CONFIGURE_FLAGS = DISABLE_SETGID_MAIL=1 \
+                       NMHETCDIRINST='$${abs_builddir}/test/testdir/inst'
 
 ##
 ## Stuff that should be cleaned via "make clean"
@@ -198,9 +198,9 @@ noinst_LIBRARIES = sbr/libmh.a mts/libmts.a
 noinst_HEADERS = h/addrsbr.h h/aliasbr.h h/crawl_folders.h h/dropsbr.h \
                 h/fmt_compile.h h/fmt_scan.h h/icalendar.h h/md5.h h/mf.h \
                 h/mh.h h/mhcachesbr.h h/mhparse.h h/mime.h \
-                h/mts.h h/nmh.h h/picksbr.h h/popsbr.h h/prototypes.h \
-                h/rcvmail.h h/scansbr.h h/signals.h h/tws.h h/utils.h \
-                mts/smtp/smtp.h sbr/ctype-checked.h h/oauth.h \
+                h/mts.h h/nmh.h h/netsec.h h/picksbr.h h/popsbr.h \
+                h/prototypes.h h/rcvmail.h h/scansbr.h h/signals.h h/tws.h \
+                h/utils.h mts/smtp/smtp.h sbr/ctype-checked.h h/oauth.h \
                 thirdparty/jsmn/jsmn.h
 
 ##
@@ -241,7 +241,8 @@ dist_doc_DATA = COPYRIGHT INSTALL NEWS README VERSION \
 ##
 docs_contribdir = $(docdir)/contrib
 dist_docs_contrib_SCRIPTS = docs/contrib/replyfilter docs/contrib/build_nmh \
-                           docs/contrib/ml docs/contrib/vpick
+                           docs/contrib/localpostproc docs/contrib/ml \
+                           docs/contrib/vpick docs/contrib/replaliases
 dist_docs_contrib_DATA = docs/contrib/replaliases
 
 ##
@@ -343,7 +344,8 @@ uip_forw_SOURCES = uip/forw.c uip/whatnowproc.c uip/whatnowsbr.c uip/sendsbr.c \
 uip_forw_LDADD = $(LDADD) $(READLINELIB) $(TERMLIB) $(ICONVLIB) $(POSTLINK)
 
 uip_inc_SOURCES = uip/inc.c uip/scansbr.c uip/dropsbr.c uip/popsbr.c
-uip_inc_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(SASLLIB) $(CURLLIB) $(POSTLINK)
+uip_inc_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(SASLLIB) $(CURLLIB) \
+               $(TLSLIB) $(POSTLINK)
 
 uip_install_mh_SOURCES = uip/install-mh.c
 uip_install_mh_LDADD = $(LDADD) $(POSTLINK)
@@ -390,7 +392,7 @@ uip_mhstore_SOURCES = uip/mhstore.c uip/mhparse.c uip/mhcachesbr.c \
 uip_mhstore_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(POSTLINK)
 
 uip_msgchk_SOURCES = uip/msgchk.c uip/popsbr.c
-uip_msgchk_LDADD = $(LDADD) $(SASLLIB) $(CURLLIB) $(POSTLINK)
+uip_msgchk_LDADD = $(LDADD) $(SASLLIB) $(CURLLIB) $(TLSLIB) $(POSTLINK)
 
 uip_new_SOURCES = uip/new.c
 uip_new_LDADD = $(LDADD) $(POSTLINK)
@@ -610,7 +612,8 @@ sbr_libmh_a_SOURCES = sbr/addrsbr.c sbr/ambigsw.c sbr/atooi.c sbr/arglist.c \
                      sbr/m_draft.c sbr/m_getfld.c sbr/m_gmprot.c \
                      sbr/m_maildir.c sbr/m_name.c sbr/m_popen.c sbr/m_rand.c \
                      sbr/makedir.c sbr/md5.c sbr/message_id.c \
-                     sbr/mime_type.c sbr/mts.c sbr/norm_charmap.c sbr/path.c \
+                     sbr/mime_type.c sbr/mts.c sbr/netsec.c \
+                     sbr/norm_charmap.c sbr/path.c \
                      sbr/peekc.c sbr/pidwait.c sbr/pidstatus.c \
                      sbr/print_help.c sbr/print_sw.c sbr/print_version.c \
                      sbr/push.c sbr/putenv.c sbr/refile.c sbr/remdir.c \
index f1dc37b..d3fa76e 100644 (file)
@@ -469,12 +469,12 @@ dnl -----------------
 
 AS_IF([test x"$tls_support" = x"yes"],[
   dnl OpenBSD 5 needs the other-libraries (fourth argument) to the
-  dnl AC_CHECK_LIB for SSL_library_init, because it doesn't
+  dnl AC_CHECK_LIB for SSL_new, because it doesn't
   dnl automatically append -lcrypto when linking with -lssl.
   AC_CHECK_HEADER([openssl/ssl.h], , [AC_MSG_ERROR([openssl/ssl.h not found])])
   AC_CHECK_LIB([crypto], [BIO_write], [TLSLIB="-lcrypto"],
     [AC_MSG_ERROR([OpenSSL crypto library not found])])
-  AC_CHECK_LIB([ssl], [SSL_library_init], [TLSLIB="-lssl $TLSLIB"],
+  AC_CHECK_LIB([ssl], [SSL_new], [TLSLIB="-lssl $TLSLIB"],
     [AC_MSG_ERROR([OpenSSL library not found])],[$TLSLIB])],
   [TLSLIB=])
 AC_SUBST([TLSLIB])
diff --git a/docs/contrib/localpostproc b/docs/contrib/localpostproc
new file mode 100755 (executable)
index 0000000..c8f7170
--- /dev/null
@@ -0,0 +1,89 @@
+#!/bin/sh
+#
+# localpostproc - A sample postproc which changes the submission email server
+#                based on user-supplied criteria.
+#
+# The basic concept is that we change where we submit mail to based on the
+# message contents.  We use scan(1) to get out fields we care about.  But
+# really, you could use ANY criteria, such as environment variables,
+# recipients, etc etc.
+#
+
+#
+# Find out which message is the draft message; yes, this sucks.
+#
+# The case statement has to know about switches that take arguments;
+# add to this list as necessary.
+#
+
+whom=0
+
+find_draftmessage() {
+       while test $# -gt 0; do
+               case "$1" in
+               -al* | -filt* | -wi* | -client | -idanno | -server | \
+               -partno | -saslmech | -user | -por* | -width | \
+               -file* | -mhl* | -mt* | -cr* | -lib* | -auth* | -sendmail)
+                       shift
+                       ;;
+               -whom)
+                       whom=1
+                       ;;
+               -*)     ;;
+               *)      
+                       draftmessage="$1"
+                       return 0
+                       ;;
+               esac
+               shift
+       done
+
+       echo "Cannot find draft message name in argument list"
+       exit 1
+}
+
+realpost="$(mhparam libdir)/post"
+
+if [ $# -eq 0 ]; then
+       echo "Usage: [post switches] filename"
+       exit 1
+fi
+
+find_draftmessage "$@"
+
+if [ $whom -eq 1 ]; then
+       exec "$realpost" "$@"
+fi
+
+fromhost=$(scan -format '%<{resent-from}%(host{resent-from})%|%(host{from})%>' -file "$draftmessage")
+
+if [ $? -ne 0 ]; then
+       echo "Unable to run scan on draft file $draftmessage, aborting"
+       exit 1
+fi
+
+if [ -z "$fromhost" ]; then
+       echo "Could not determine hostname of From: address"
+       exit 1;
+fi
+
+#
+# Here we use the hostname in the "from" address, but you could use anything
+#
+
+case "$fromhost" in
+       *host1.com)
+       postflags="-server smtp.host1.com -sasl -port submission"
+       ;;
+
+       host2.com)
+       postflags="-server smtp.host2.com -sasl -tls -port submission"
+       ;;
+
+       *)
+       echo "Don't know how to send email from $fromhost"
+       exit 1
+       ;;
+esac
+
+exec "$realpost" $postflags "$@"
index 6848dc2..796e7b2 100644 (file)
@@ -1,7 +1,13 @@
 #### replaliases
 ####
 #### convenience functions for various repl(1) commands
-#### They're functions instead of aliases for portability.
+####
+#### They're functions instead of aliases for portability.  This file
+#### is intended to be sourced from a Bourne-compatible shell, e.g.,
+####     source `mhparam docdir`/contrib/replaliases
+#### to declare the functions.
+####
+#### Author: David Levine <levinedl@acm.org>
 
 #### If using par (see mhn.defaults), it helps to have its PARINIT
 #### environment variable set.  If you really want it to be null,
index 70ed4ff..f44eb06 100644 (file)
@@ -48,8 +48,10 @@ NEW FEATURES
 - post(8) -snoop now attempts to decode base64-encoded SMTP traffic.
 - folder(1) -nocreate now prints a warning message for a non-existant folder.
 - mhfixmsg(1) now allows -decodetext binary, though 8bit is still the default.
+- inc(1) and msgchk(1) now support TLS encryption natively.
 - Support for SMTPUTF8 (RFC 6531) has been added.  mhshow(1) already supported
-  RFC 6532, assuming all 8-bit message header field bodies are UTF-8.
+  RFC 6532, assuming all 8-bit message header field bodies are UTF-8 and use
+  of a UTF-8 locale.
 
 -----------------
 OBSOLETE FEATURES
diff --git a/h/netsec.h b/h/netsec.h
new file mode 100644 (file)
index 0000000..e05e79f
--- /dev/null
@@ -0,0 +1,405 @@
+/*
+ * Network security library routines for nmh.
+ *
+ * These are a common set of routines to handle network security for
+ * things like SASL and OpenSSL.
+ */
+
+struct _netsec_context;
+typedef struct _netsec_context netsec_context;
+
+/*
+ * Create a network security context.  Returns the allocated network
+ * security context.  Cannot fail.
+ */
+
+netsec_context *netsec_init(void);
+
+/*
+ * Shuts down the security context for a connection and frees all
+ * associated resources.
+ *
+ * Arguments:
+ *
+ * ns_context  - Network security context
+ * closeflag   - If set to 1, close the socket descriptor as well.
+ */
+
+void netsec_shutdown(netsec_context *ns_context, int closeflag);
+
+/*
+ * Sets the file descriptor for this connection.  This will be used by
+ * the underlying communication.
+ *
+ * Arguments:
+ *
+ * ns_context  - Network security context
+ * readfd      - Read file descriptor of remote connection.
+ * writefd     - Write file descriptor of remote connection
+ */
+
+void netsec_set_fd(netsec_context *ns_context, int readfd, int writefd);
+
+/*
+ * Set the userid used to authenticate to this connection.
+ *
+ * Arguments:
+ *
+ * ns_context  - Network security context
+ * userid      - Userid to be used for authentication.  Cannot be NULL.
+ */
+
+void netsec_set_userid(netsec_context *ns_context, const char *userid);
+
+/*
+ * Returns "snoop" status on current connection.
+ *
+ * Arguments:
+ *
+ * ns_context  - Network security context
+ *
+ * Returns "1" if snoop is enabled, 0 if it is not.
+ */
+
+int netsec_get_snoop(netsec_context *ns_context);
+
+/*
+ * Sets "snoop" status; if snoop is set to a nonzero value, network traffic
+ * will be logged on standard error.
+ *
+ * Arguments:
+ *
+ * ns_context  - Network security context
+ * snoop       - Integer value; set to nonzero to enable traffic logging
+ */
+
+void netsec_set_snoop(netsec_context *ns_context, int snoop);
+
+/*
+ * A callback designed to handle the snoop output; it can be used by
+ * a protocol to massage the data in a more user-friendly way.
+ *
+ * Arguments:
+ *
+ * ns_context  - Network security context
+ * string      - String to output
+ * len         - Length of string
+ * context     - "Extra" context information to be used by callback.
+ */
+
+typedef void (netsec_snoop_callback)(netsec_context *ns_context,
+                                    const char *string, size_t len,
+                                    void *context);
+
+/*
+ * Set the snoop callback function; will be used to handle protocol-specific
+ * messages.  Set to NULL to disable.
+ *
+ * Arguments:
+ *
+ * ns_context  - Network security context
+ * callback    - Snoop callback
+ * context     - Extra context information to be passed to callback.
+ */
+
+void netsec_set_snoop_callback(netsec_context *ns_context,
+                              netsec_snoop_callback *callback, void *context);
+
+/*
+ * A sample callback protocols can utilize; decode base64 tokens in the
+ * output.  The context is a pointer to an int which contains an offset
+ * into the data to start decoding.
+ */
+
+extern netsec_snoop_callback netsec_b64_snoop_decoder;
+
+/*
+ * Set the read timeout for this connection.
+ *
+ * Arguments:
+ *
+ * ns_context  - Network security context
+ * timeout     - Read timeout, in seconds.
+ */
+
+void netsec_set_timeout(netsec_context *ns_context, int timeout);
+
+/*
+ * Read a "line" from the network.  This reads one CR/LF terminated line.
+ * Returns a pointer to a NUL-terminated string.  This memory is valid
+ * until the next call to any read function.  Will return an error if
+ * the line does not terminate with a CR/LF.
+ *
+ * Arguments:
+ *
+ * ns_context  - Network security context
+ * length      - Returned length of string
+ * errstr      - Error string
+ *
+ * Returns pointer to string, or NULL on error.
+ */
+
+char *netsec_readline(netsec_context *ns_context, size_t *length,
+                     char **errstr);
+
+/*
+ * Read bytes from the network.
+ *
+ * Arguments:
+ *
+ * ns_context  - Network security context
+ * buffer      - Read buffer
+ * size                - Buffer size
+ * errstr      - Error size
+ *
+ * Returns number of bytes read, or -1 on error.
+ */
+
+ssize_t netsec_read(netsec_context *ns_context, void *buffer, size_t size,
+                   char **errstr);
+
+/*
+ * Write data to the network; if encryption is being performed, we will
+ * do it.  Data may be buffered; use netsec_flush() to flush any outstanding
+ * data to the network.
+ *
+ * Arguments:
+ *
+ * ns_context  - Network security context
+ * buffer      - Output buffer to write to network
+ * size                - Size of data to write to network
+ * errstr      - Error string
+ *
+ * Returns OK on success, NOTOK otherwise.
+ */
+
+int netsec_write(netsec_context *ns_context, const void *buffer, size_t size,
+                char **errstr);
+
+/*
+ * Write bytes using printf formatting
+ *
+ * Arguments:
+ *
+ * ns_context  - Network security context
+ * errstr      - Error string
+ * format      - Format string
+ * ...         - Arguments for format string
+ *
+ * Returns OK on success, NOTOK on error.
+ */
+
+int netsec_printf(netsec_context *ns_context, char **errstr,
+                 const char *format, ...);
+
+/*
+ * Write bytes using a va_list argument.
+ *
+ * Arguments:
+ *
+ * ns_context  - Network security context
+ * errstr      - Error string
+ * format      - Format string
+ * ap          - stdarg list.
+ *
+ * Returns OK on success, NOTOK on error.
+ */
+
+int netsec_vprintf(netsec_context *ns_context, char **errstr,
+                  const char *format, va_list ap);
+
+/*
+ * Flush any buffered bytes to the network.
+ *
+ * Arguments:
+ *
+ * ns_context  - Network security context
+ * errstr      - Error string
+ *
+ * Returns OK on success, NOTOK on error.
+ */
+
+int netsec_flush(netsec_context *ns_context, char **errstr);
+
+/*
+ * Enumerated types for the type of message we are sending/receiving.
+ */
+
+enum sasl_message_type {
+       NETSEC_SASL_START,      /* Start of SASL authentication */
+       NETSEC_SASL_READ,       /* Reading a message */
+       NETSEC_SASL_WRITE,      /* Writing a message */
+       NETSEC_SASL_FINISH,     /* Complete SASL exchange */
+       NETSEC_SASL_CANCEL      /* Cancel a SASL exchange */
+};
+
+/*
+ * The SASL callback; this is designed to parse a protocol-specific
+ * message and return the SASL protocol message back.
+ *
+ * The meaning of the arguments to the callback depend on the mtype
+ * arguments.  See below for more detail.
+ *
+ * Arguments:
+ *
+ * mtype       - The type of message we are processing (read/write/cancel).
+ * indata      - A pointer to any input data.
+ * indatasize  - The size of the input data in bytes
+ * outdata     - Output data (freed by caller)
+ * outdatasize - Size of output data
+ * errstr      - An error string to be returned (freed by caller).
+ *
+ * As a general note, plugins should perform their own I/O.  Buffers returned
+ * by NETSEC_SASL_READ should be allocated by the plugins and will be freed
+ * by the netsec package.  Error messages returned should be created by
+ * netsec_err().
+ *
+ * Parameter interpretation based on mtype value:
+ *
+ * NETSEC_SASL_START   - Create a protocol message that starts SASL
+ *                       authentication.  If an initial response is
+ *                       supported, indata and indatasize will contain it.
+ *                       Otherwise they will be set to NULL and 0.
+ * NETSEC_SASL_READ    - Parse and decode a protocol message and extract
+ *                       out the SASL payload data.  indata will be set
+ *                       to NULL; the callback must read in the necessary
+ *                       data using the appropriate netsec function.
+ *                       outdata/outdatasize should contain the decoded
+ *                       SASL message (again, must be free()d by the caller).
+ * NETSEC_SASL_WRITE   - Generate a protocol message to send over the
+ *                       network.  indata/indatasize will contain the
+ *                       SASL payload data.
+ * NETSEC_SASL_FINISH  - Process the final SASL message exchange; at
+ *                       this point SASL exchange should have completed
+ *                       and we should get a message back from the server
+ *                       telling us whether or not authentication is
+ *                       successful.  All buffer parameters are NULL.
+ * NETSEC_SASL_CANCEL  - Generate a protocol message that cancels the
+ *                       SASL protocol exchange; outdata/outdatasize
+ *                       should contain this message.
+ *
+ * The callback should return OK on success, NOTOK on failure.  Depending
+ * at the point of the authentication exchange, the callback may be asked
+ * to generate a cancel message.
+ */
+
+typedef int (*netsec_sasl_callback)(enum sasl_message_type mtype,
+                                   unsigned const char *indata,
+                                   unsigned int indatasize,
+                                   unsigned char **outdata,
+                                   unsigned int *outdatasize, char **errstr);
+
+/*
+ * Sets the SASL parameters for this connection.  If this function is
+ * not called or is called with NULL for arguments, SASL authentication
+ * will not be attempted for this connection.
+ *
+ * The caller must provide a callback to parse the protocol and return
+ * the SASL protocol messages (see above for callback details).
+ *
+ * Arguments:
+ *
+ * ns_context  - Network security context
+ * hostname    - Fully qualified hostname of remote host.
+ * service     - Service name (set to NULL to disable SASL).
+ * mechanism   - The mechanism desired by the user.  If NULL, the SASL
+ *               library will attempt to negotiate the best mechanism.
+ * callback    - SASL protocol callbacks 
+ * errstr      - Error string.
+ *
+ * Returns NOTOK if SASL is not supported.
+ */
+
+int netsec_set_sasl_params(netsec_context *ns_context, const char *hostname,
+                          const char *service, const char *mechanism,
+                          netsec_sasl_callback callback, char **errstr);
+
+/*
+ * Start SASL negotiation.  The Netsec library will use the callbacks
+ * supplied in netsec_set_sasl_params() to format and parse the protocol
+ * messages.
+ *
+ * Arguments:
+ *
+ * ns_context  - Network security context
+ * mechlist    - Space-separated list of supported SASL mechanisms
+ * errstr      - An error string to be returned upon error.
+ *
+ * Returns OK on success, NOTOK on failure.
+ */
+
+int netsec_negotiate_sasl(netsec_context *ns_context, const char *mechlist,
+                         char **errstr);
+
+/*
+ * Returns the chosen SASL mechanism by the SASL library or netsec.
+ *
+ * Arguments:
+ *
+ * ns_context  - Network security context
+ *
+ * Returns a string containing the chosen mech, or NULL if SASL is not
+ * supported or in use.
+ */
+
+char *netsec_get_sasl_mechanism(netsec_context *ns_context);
+
+/*
+ * Set the OAuth service name used to retrieve the OAuth parameters from
+ * user's profile.  Just calling this function is not enough to guarantee
+ * that XOAUTH2 authentication will be performed; the appropriate mechanism
+ * name must be passed into netsec_set_sasl_params().
+ *
+ * Arguments:
+ *
+ * ns_context  - Network security context
+ * service     - OAuth2 service names.
+ *
+ * Returns NOTOK if OAuth2 is not supported.
+ */
+
+int netsec_set_oauth_service(netsec_context *ns_context, const char *service);
+
+/*
+ * Controls whether or not TLS will be negotiated for this connection.
+ *
+ * Note: callers still have to call netsec_tls_negotiate() to start
+ * TLS negotiation at the appropriate point in the protocol.
+ *
+ * Arguments
+ *
+ * tls         - If nonzero, enable TLS. Otherwise disable TLS
+ *               negotiation.
+ *
+ * Returns NOTOK if TLS is not supported or was unable to initialize.
+ */
+
+int netsec_set_tls(netsec_context *context, int tls, char **errstr);
+
+/*
+ * Start TLS negotiation on this protocol.  This connection should have
+ * netsec_set_tls() called on it.
+ *
+ * Arguments:
+ *
+ * ns_context  - Network security context
+ * errstr      - Error string upon failure.
+ *
+ * Returns OK on success, NOTOK on failure.
+ */
+
+int netsec_negotiate_tls(netsec_context *ns_context, char **errstr);
+
+/*
+ * Allocate and format an error string; should be used by plugins
+ * to report errors.
+ *
+ * Arguments:
+ *
+ * errstr      - Error string to be returned
+ * format      - printf(3) format string
+ * ...         - Arguments to printf(3)
+ *
+ */
+
+void netsec_err(char **errstr, const char *format, ...);
index 274ca9d..0278cae 100644 (file)
--- a/h/oauth.h
+++ b/h/oauth.h
@@ -105,14 +105,18 @@ struct mh_oauth_service_info {
  * Do the complete dance for XOAUTH2 as used by POP3 and SMTP.
  *
  * Load tokens for svc from disk, refresh if necessary, and return the
- * base64-encoded client response.
+ * client response in client_response and client_response_len.
  *
  * If refreshing, writes freshened tokens to disk.
  *
  * Exits via adios on any error.
+ *
+ * Always returns OK for now, but in the future could return NOTOK on error.
  */
-char *
-mh_oauth_do_xoauth(const char *user, const char *svc, FILE *log);
+
+int
+mh_oauth_do_xoauth(const char *user, const char *svc, unsigned char **oauth_res,
+                  size_t *oauth_res_len, FILE *log);
 
 /*
  * Allocate and initialize a new OAuth context.
index 3fb4179..c155fd1 100644 (file)
@@ -3,7 +3,7 @@
  * popsbr.h -- header for POP client subroutines
  */
 
-int pop_init (char *, char *, char *, char *, char *, int, int, char *,
+int pop_init (char *, char *, char *, char *, char *, int, int, char *, int,
               const char *);
 int pop_fd (char *, int, char *, int);
 int pop_stat (int *, int *);
index 5c1b5cf..bc5b6a5 100644 (file)
@@ -457,6 +457,10 @@ int what_now (char *, int, int, char *, char *,
        int, struct msgs *, char *, int, char *, int);
 int WhatNow(int, char **);
 
+/* Includes trailing NUL */
+
+#define BASE64SIZE(x) ((((x + 2) / 3) * 4) + 1)
+
 /*
  * Copy data from one file to another, converting to base64-encoding.
  *
@@ -470,8 +474,8 @@ int WhatNow(int, char **);
  */
 int writeBase64aux(FILE *in, FILE *out, int crlf);
 
-int writeBase64 (unsigned char *, size_t, unsigned char *);
-int writeBase64raw (unsigned char *, size_t, unsigned char *);
+int writeBase64 (const unsigned char *, size_t, unsigned char *);
+int writeBase64raw (const unsigned char *, size_t, unsigned char *);
 
 /*
  * first argument:  the string to be decoded
@@ -480,7 +484,8 @@ int writeBase64raw (unsigned char *, size_t, unsigned char *);
  * fourth argument:   non-zero for text content, and for which CR's should be skipped
  * fifth argument:  for an MD5 digest, it can be null
  */
-int decodeBase64 (const char *, const char **, size_t *, int, unsigned char *);
+int decodeBase64 (const char *, unsigned char **, size_t *, int,
+                 unsigned char *);
 
 void hexify (const unsigned char *, size_t, char **);
 
index b8a9245..c6e5618 100644 (file)
@@ -39,6 +39,8 @@ inc \- incorporate new mail
 .IR mechanism ]
 .RB [ \-authservice
 .IR service ]
+.RB [ \-initialtls ]
+.RB [ \-notls ]
 .RB [ \-snoop ]
 .RB [ \-version ]
 .RB [ \-help ]
@@ -276,8 +278,10 @@ for its other features.
 If
 .B nmh
 has been compiled with OAuth support, the
+.B \-sasl
+and
 .B \-saslmech xoauth2
-switch will enable OAuth authentication.  The
+switches will enable OAuth authentication.  The
 .B \-user
 switch must be used, and the
 .I user-name
@@ -291,10 +295,21 @@ and grant authorization to that account.  See the
 .B mhlogin
 man page for more details.
 .PP
-Gmail only supports POP3 over TLS, but
-.B inc
-has no TLS support.  To work around this, use something like
-.B -proxy 'openssl s_client -connect %h:995 -CAfile /etc/ssl/certs/ca-certificates.crt -quiet'
+If
+.B nmh
+has been compiled with TLS support, the
+.B \-initialtls
+switch will require the negotiation of TLS when connecting
+to the remote POP server.  The
+.B \-initialtls
+switch will negotiate TLS immediately after the connection has taken place,
+before any POP commands are sent or received.  Data encrypted by TLS is
+labeled `(tls-encrypted)' and `(tls-decrypted)` with viewing the POP
+transaction with the
+.B \-snoop
+switch.  The
+.B \-notls
+switch will disable all attempts to negotiate TLS.
 .SH FILES
 .PD 0
 .TP 20
index 1023676..55d63b8 100644 (file)
@@ -32,8 +32,6 @@ post \- deliver a message
 .IR portname/number ]
 .RB [ \-sasl ]
 .RB [ \-nosasl ]
-.RB [ \-saslmaxssf
-.IR ssf ]
 .RB [ \-saslmech
 .IR mechanism ]
 .RB [ \-user
@@ -236,11 +234,6 @@ switch.
 Base64-encoded data is wrapped with `b64<>'.
 (Beware that the SMTP transaction may contain authentication information either
 in plaintext or easily decoded base64.)
-The
-.B \-saslmaxssf
-switch can be used to select the maximum value of the Security Strength Factor.
-This is an integer value and the exact meaning of this value depends on the
-underlying SASL mechanism.  A value of 0 disables encryption.
 .PP
 If
 .B nmh
index 680f1d4..5a2f313 100644 (file)
@@ -40,8 +40,6 @@ send \- send a message
 .IR port-name/number ]
 .RB [ \-sasl ]
 .RB [ \-nosasl ]
-.RB [ \-saslmaxssf
-.IR ssf ]
 .RB [ \-saslmech
 .IR mechanism ]
 .RB [ \-authservice
@@ -427,17 +425,14 @@ switch; see the
 man page description of
 .B \-snoop
 for its other features.
-The
-.B \-saslmaxssf
-switch can be used to select the maximum value of the Security Strength Factor.
-This is an integer value and the exact meaning of this value depends on the
-underlying SASL mechanism.  A value of 0 disables encryption.
 .PP
 If
 .B nmh
 has been compiled with OAuth support, the
+.B \-sasl
+and
 .B \-saslmech xoauth2
-switch will enable OAuth authentication.  The
+switches will enable OAuth authentication.  The
 .B \-user
 switch must be used, and the
 .I user-name
@@ -448,7 +443,7 @@ be specified with the
 switch.  Before using this, the user must authorize nmh by running
 .B mhlogin
 and grant authorization to that account.  See the
-.B mhlogin
+.IR mhlogin (1)
 man page for more details.
 .PP
 If
index cbad25f..2e992e7 100644 (file)
@@ -29,6 +29,7 @@ whom \- report to whom a message would go
 .RB [ \-user
 .IR username ]
 .RB [ \-tls ]
+.RB [ \-initialtls ]
 .RB [ \-notls ]
 .RI [ file ]
 .RB [ \-draft ]
@@ -106,16 +107,13 @@ switch; see the
 .B post
 man page description of
 .B \-snoop
-for its other features.  The
-.B \-saslmaxssf
-switch can be used to select the maximum value of the Security Strength Factor.
-This is an integer value and the exact meaning of this value depends on the
-underlying SASL mechanism.  A value of 0 disables encryption.
+for its other features.
 .PP
 If
 .B nmh
 has been compiled with TLS support, the
-.B \-tls
+.BR \-tls ,
+.BR \-initialtls ,
 and
 .B \-notls
 switches will require and disable the negotiation of TLS support when connecting to the
@@ -126,7 +124,7 @@ switch; see the
 .B post
 man page description of
 .B \-snoop
-for its other features.
+and the TLS flags for more details.
 .PP
 The files specified by the profile entry \*(lqAliasfile:\*(rq and any
 additional alias files given by the
index 47215a8..b5e68fa 100644 (file)
 #include <h/mts.h>
 #include <h/signals.h>
 #include <h/utils.h>
-#include <h/oauth.h>
-
-#ifdef CYRUS_SASL
-#include <sasl/sasl.h>
-#include <sasl/saslutil.h>
-# if SASL_VERSION_FULL < 0x020125
-    /* Cyrus SASL 2.1.25 introduced the sasl_callback_ft prototype,
-       which has an explicit void parameter list, according to best
-       practice.  So we need to cast to avoid compile warnings.
-       Provide this prototype for earlier versions. */
-    typedef int (*sasl_callback_ft)();
-# endif /* SASL_VERSION_FULL < 0x020125 */
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <netdb.h>
-#endif /* CYRUS_SASL */
+#include <h/netsec.h>
 
-#ifdef TLS_SUPPORT
-#include <openssl/ssl.h>
-#include <openssl/err.h>
-#endif /* TLS_SUPPORT */
+#include <sys/socket.h>
 
 /*
  * This module implements an interface to SendMail very similar
 #define        SM_AUTH  45
 
 static int sm_addrs = 0;
-static int sm_alarmed = 0;
 static int sm_child = NOTOK;
 static int sm_debug = 0;
 static int sm_nl = TRUE;
 static int sm_verbose = 0;
-
-static FILE *sm_rfp = NULL;
-static FILE *sm_wfp = NULL;
-
-static int next_line_encoded = 0;
-
-#ifdef CYRUS_SASL
-/*
- * Some globals needed by SASL
- */
-
-static sasl_conn_t *conn = NULL;       /* SASL connection state */
-static int sasl_complete = 0;          /* Has authentication succeeded? */
-static sasl_ssf_t sasl_ssf;            /* Our security strength factor */
-static int maxoutbuf;                  /* Maximum crypto output buffer */
-static char *sasl_outbuffer;           /* SASL output buffer for encryption */
-static int sasl_outbuflen;             /* Current length of data in outbuf */
-static int sm_get_user(void *, int, const char **, unsigned *);
-static int sm_get_pass(sasl_conn_t *, void *, int, sasl_secret_t **);
-
-static sasl_callback_t callbacks[] = {
-    { SASL_CB_USER, (sasl_callback_ft) sm_get_user, NULL },
-#define SM_SASL_N_CB_USER 0
-    { SASL_CB_AUTHNAME, (sasl_callback_ft) sm_get_user, NULL },
-#define SM_SASL_N_CB_AUTHNAME 1
-    { SASL_CB_PASS, (sasl_callback_ft) sm_get_pass, NULL },
-#define SM_SASL_N_CB_PASS 2
-    { SASL_CB_LIST_END, NULL, NULL },
-};
-
-#else /* CYRUS_SASL */
-int sasl_ssf = 0;
-#endif /* CYRUS_SASL */
-
-#ifdef TLS_SUPPORT
-static SSL_CTX *sslctx = NULL;
-static SSL *ssl = NULL;
-static BIO *sbior = NULL;
-static BIO *sbiow = NULL;
-static BIO *io = NULL;
-
-static int tls_negotiate(void);
-#endif /* TLS_SUPPORT */
-
-#if defined(CYRUS_SASL) || defined(TLS_SUPPORT)
-#define SASL_MAXRECVBUF 65536
-static int sm_fgetc(FILE *);
-static char *sasl_inbuffer;            /* SASL input buffer for encryption */
-static char *sasl_inptr;               /* Pointer to current inbuf position */
-static int sasl_inbuflen;              /* Current length of data in inbuf */
-#else
-#define sm_fgetc fgetc
-#endif
-
-static int tls_active = 0;
+static netsec_context *nsc = NULL;
 
 static char *sm_noreply = "No reply text given";
 static char *sm_moreply = "; ";
@@ -153,65 +79,42 @@ static char *EHLOkeys[MAXEHLO + 1];
 /*
  * static prototypes
  */
-static int smtp_init (char *, char *, char *, int, int, int, int, int,
-                     char *, char *, const char *, int);
-static int sendmail_init (char *, char *, int, int, int, int, int,
-                          char *, char *);
+static int smtp_init (char *, char *, char *, int, int, int, int, const char *,
+                     const char *, const char *, int);
+static int sendmail_init (char *, int, int, int, int, const char *,
+                         const char *);
 
 static int rclient (char *, char *);
-static int sm_ierror (char *fmt, ...);
+static int sm_ierror (const char *fmt, ...);
+static int sm_nerror (char *);
 static int smtalk (int time, char *fmt, ...);
-static int sm_wrecord (char *, int);
 static int sm_wstream (char *, int);
-static int sm_werror (void);
 static int smhear (void);
-static int sm_rrecord (char *, int *);
-static int sm_rerror (int);
-static void alrmser (int);
 static char *EHLOset (char *);
-static char *prepare_for_display (const char *, int *);
-static int sm_fwrite(char *, int);
-static int sm_fputs(char *);
-static int sm_fputc(int);
-static void sm_fflush(void);
-static int sm_fgets(char *, int, FILE *);
-static int sm_auth_xoauth2(const char *, const char *, int);
-
-#ifdef CYRUS_SASL
-/*
- * Function prototypes needed for SASL
- */
-
-static int sm_auth_sasl(char *, int, char *, char *);
-#endif /* CYRUS_SASL */
+static int sm_sasl_callback(enum sasl_message_type, unsigned const char *,
+                           unsigned int, unsigned char **, unsigned int *,
+                           char **);
 
 int
 sm_init (char *client, char *server, char *port, int watch, int verbose,
-         int debug, int sasl, int saslssf, char *saslmech, char *user,
+         int debug, int sasl, const char *saslmech, const char *user,
          const char *oauth_svc, int tls)
 {
     if (sm_mts == MTS_SMTP)
        return smtp_init (client, server, port, watch, verbose,
-                         debug, sasl, saslssf, saslmech, user,
-                          oauth_svc, tls);
+                         debug, sasl, saslmech, user, oauth_svc, tls);
     else
-       return sendmail_init (client, server, watch, verbose,
-                              debug, sasl, saslssf, saslmech, user);
+       return sendmail_init (client, watch, verbose, debug, sasl,
+                             saslmech, user);
 }
 
 static int
 smtp_init (char *client, char *server, char *port, int watch, int verbose,
-          int debug,
-           int sasl, int saslssf, char *saslmech, char *user,
+          int debug, int sasl, const char *saslmech, const char *user,
            const char *oauth_svc, int tls)
 {
-    int result, sd1, sd2;
-#ifndef CYRUS_SASL
-    NMH_UNUSED (sasl);
-    NMH_UNUSED (saslssf);
-    NMH_UNUSED (saslmech);
-    NMH_UNUSED (user);
-#endif /* CYRUS_SASL */
+    int result, sd1;
+    char *errstr;
 
     if (watch)
        verbose = TRUE;
@@ -219,7 +122,7 @@ smtp_init (char *client, char *server, char *port, int watch, int verbose,
     sm_verbose = verbose;
     sm_debug = debug;
 
-    if (sm_rfp != NULL && sm_wfp != NULL)
+    if (nsc != NULL)
        goto send_options;
 
     if (client == NULL || *client == '\0') {
@@ -237,35 +140,37 @@ smtp_init (char *client, char *server, char *port, int watch, int verbose,
     if (client == NULL || *client == '\0')
        client = "localhost";
 
-#if defined(CYRUS_SASL) || defined(TLS_SUPPORT)
-    sasl_inbuffer = malloc(SASL_MAXRECVBUF);
-    if (!sasl_inbuffer)
-       return sm_ierror("Unable to allocate %d bytes for read buffer",
-                        SASL_MAXRECVBUF);
-#endif /* CYRUS_SASL || TLS_SUPPORT */
+    nsc = netsec_init();
 
-    if ((sd1 = rclient (server, port)) == NOTOK)
-       return RP_BHST;
+    if (user)
+       netsec_set_userid(nsc, user);
+
+    if (sm_debug)
+       netsec_set_snoop(nsc, 1);
 
-    if ((sd2 = dup (sd1)) == NOTOK) {
-       close (sd1);
-       return sm_ierror ("unable to dup");
+    if (sasl) {
+       if (netsec_set_sasl_params(nsc, server, "smtp", saslmech,
+                                  sm_sasl_callback, &errstr) != OK)
+           return sm_nerror(errstr);
+    }
+
+    if (oauth_svc) {
+       if (netsec_set_oauth_service(nsc, oauth_svc) != OK)
+           return sm_ierror("OAuth2 not supported");
     }
 
-    SIGNAL (SIGALRM, alrmser);
+    if ((sd1 = rclient (server, port)) == NOTOK)
+       return RP_BHST;
+
     SIGNAL (SIGPIPE, SIG_IGN);
 
-    if ((sm_rfp = fdopen (sd1, "r")) == NULL
-           || (sm_wfp = fdopen (sd2, "w")) == NULL) {
-       close (sd1);
-       close (sd2);
-       sm_rfp = sm_wfp = NULL;
-       return sm_ierror ("unable to fdopen");
-    }
+    netsec_set_fd(nsc, sd1, sd1);
 
-    tls_active = 0;
+    if (tls) {
+       if (netsec_set_tls(nsc, 1, &errstr) != OK)
+           return sm_nerror(errstr);
+    }
 
-#ifdef TLS_SUPPORT
     /*
      * If tls == 2, that means that the user requested "initial" TLS,
      * which happens right after the connection has opened.  Do that
@@ -273,21 +178,14 @@ smtp_init (char *client, char *server, char *port, int watch, int verbose,
      */
 
     if (tls == 2) {
-       result = tls_negotiate();
-
-       /*
-        * Note: if tls_negotiate() fails it will call sm_end() for us,
-        * which closes the connection.
-        */
-       if (result != RP_OK)
-           return result;
+       if (netsec_negotiate_tls(nsc, &errstr) != OK) {
+           sm_end(NOTOK);
+           return sm_nerror(errstr);
+       }
     }
-#endif /* TLS_SUPPORT */
 
-    sm_alarmed = 0;
-    alarm (SM_OPEN);
+    netsec_set_timeout(nsc, SM_OPEN);
     result = smhear ();
-    alarm (0);
 
     switch (result) {
        case 220: 
@@ -314,7 +212,6 @@ smtp_init (char *client, char *server, char *port, int watch, int verbose,
        return RP_RPLY;
     }
 
-#ifdef TLS_SUPPORT
     /*
      * If the user requested TLS support, then try to do the STARTTLS command
      * as part of the initial dialog.  Assuming this works, we then need to
@@ -339,10 +236,10 @@ smtp_init (char *client, char *server, char *port, int watch, int verbose,
         * negotiation.  Oblige them.
         */
 
-       result = tls_negotiate();
-
-       if (result != RP_OK)
-           return result;
+       if (netsec_negotiate_tls(nsc, &errstr) != OK) {
+           sm_end(NOTOK);
+           return sm_nerror(errstr);
+       }
 
        doingEHLO = 1;
        result = smtalk (SM_HELO, "EHLO %s", client);
@@ -353,11 +250,7 @@ smtp_init (char *client, char *server, char *port, int watch, int verbose,
            return RP_RPLY;
        }
     }
-#else  /* TLS_SUPPORT */
-    NMH_UNUSED (tls);
-#endif /* TLS_SUPPORT */
 
-#ifdef CYRUS_SASL
     /*
      * If the user asked for SASL, then check to see if the SMTP server
      * supports it.  Otherwise, error out (because the SMTP server
@@ -372,34 +265,9 @@ smtp_init (char *client, char *server, char *port, int watch, int verbose,
            return sm_ierror("SMTP server does not support SASL");
        }
 
-       if (saslmech && stringdex(saslmech, server_mechs) == -1) {
-           sm_end(NOTOK);
-           return sm_ierror("Requested SASL mech \"%s\" is not in the "
-                            "list of supported mechanisms:\n%s",
-                            saslmech, server_mechs);
-       }
-
-        /* Don't call sm_auth_sasl() for XAUTH2 with -sasl.  Instead, call
-           sm_auth_xoauth2() below. */
-       if (oauth_svc == NULL  &&
-            sm_auth_sasl(user, saslssf, saslmech ? saslmech : server_mechs,
-                        server) != RP_OK) {
-           sm_end(NOTOK);
-           return NOTOK;
-       }
-    }
-#endif /* CYRUS_SASL */
-
-    if (oauth_svc != NULL) {
-        char *server_mechs;
-       if ((server_mechs = EHLOset("AUTH")) == NULL
-            || stringdex("XOAUTH2", server_mechs) == -1) {
-           sm_end(NOTOK);
-           return sm_ierror("SMTP server does not support SASL XOAUTH2");
-       }
-       if (sm_auth_xoauth2(user, oauth_svc, debug) != RP_OK) {
+       if (netsec_negotiate_sasl(nsc, server_mechs, &errstr) != OK) {
            sm_end(NOTOK);
-           return NOTOK;
+           return sm_nerror(errstr);
        }
     }
 
@@ -411,28 +279,19 @@ send_options: ;
 }
 
 int
-sendmail_init (char *client, char *server, int watch, int verbose,
-               int debug, int sasl, int saslssf, char *saslmech, char *user)
+sendmail_init (char *client, int watch, int verbose, int debug, int sasl,
+              const char *saslmech, const char *user)
 {
     unsigned int i, result, vecp;
     int pdi[2], pdo[2];
-    char *vec[15];
-#ifdef CYRUS_SASL
-    char *server_mechs;
-#else  /* CYRUS_SASL */
-    NMH_UNUSED (server);
-    NMH_UNUSED (sasl);
-    NMH_UNUSED (saslssf);
-    NMH_UNUSED (saslmech);
-    NMH_UNUSED (user);
-#endif /* CYRUS_SASL */
+    char *vec[15], *errstr;
 
     if (watch)
        verbose = TRUE;
 
     sm_verbose = verbose;
     sm_debug = debug;
-    if (sm_rfp != NULL && sm_wfp != NULL)
+    if (nsc)
        return RP_OK;
 
     if (client == NULL || *client == '\0') {
@@ -449,12 +308,19 @@ sendmail_init (char *client, char *server, int watch, int verbose,
     if (client == NULL || *client == '\0')
        client = "localhost";
 
-#if defined(CYRUS_SASL) || defined(TLS_SUPPORT)
-    sasl_inbuffer = malloc(SASL_MAXRECVBUF);
-    if (!sasl_inbuffer)
-       return sm_ierror("Unable to allocate %d bytes for read buffer",
-                        SASL_MAXRECVBUF);
-#endif /* CYRUS_SASL || TLS_SUPPORT */
+    nsc = netsec_init();
+
+    if (user)
+       netsec_set_userid(nsc, user);
+
+    if (sm_debug)
+       netsec_set_snoop(nsc, 1);
+
+    if (sasl) {
+       if (netsec_set_sasl_params(nsc, client, "smtp", saslmech,
+                                  sm_sasl_callback, &errstr) != OK)
+           return sm_nerror(errstr);
+    }
 
     if (pipe (pdi) == NOTOK)
        return sm_ierror ("no pipes");
@@ -501,22 +367,14 @@ sendmail_init (char *client, char *server, int watch, int verbose,
            _exit (-1);         /* NOTREACHED */
 
        default: 
-           SIGNAL (SIGALRM, alrmser);
            SIGNAL (SIGPIPE, SIG_IGN);
 
            close (pdi[1]);
            close (pdo[0]);
-           if ((sm_rfp = fdopen (pdi[0], "r")) == NULL
-                   || (sm_wfp = fdopen (pdo[1], "w")) == NULL) {
-               close (pdi[0]);
-               close (pdo[1]);
-               sm_rfp = sm_wfp = NULL;
-               return sm_ierror ("unable to fdopen");
-           }
-           sm_alarmed = 0;
-           alarm (SM_OPEN);
+
+           netsec_set_fd(nsc, pdi[i], pdo[1]);
+           netsec_set_timeout(nsc, SM_OPEN);
            result = smhear ();
-           alarm (0);
            switch (result) {
                case 220: 
                    break;
@@ -542,34 +400,24 @@ sendmail_init (char *client, char *server, int watch, int verbose,
                    return RP_RPLY;
            }
 
-#ifdef CYRUS_SASL
-    /*
-     * If the user asked for SASL, then check to see if the SMTP server
-     * supports it.  Otherwise, error out (because the SMTP server
-     * might have been spoofed; we don't want to just silently not
-     * do authentication
-     */
-
-    if (sasl) {
-       if (! (server_mechs = EHLOset("AUTH"))) {
-           sm_end(NOTOK);
-           return sm_ierror("SMTP server does not support SASL");
-       }
-
-       if (saslmech && stringdex(saslmech, server_mechs) == -1) {
-           sm_end(NOTOK);
-           return sm_ierror("Requested SASL mech \"%s\" is not in the "
-                            "list of supported mechanisms:\n%s",
-                            saslmech, server_mechs);
-       }
-
-       if (sm_auth_sasl(user, saslssf, saslmech ? saslmech : server_mechs,
-                        server) != RP_OK) {
-           sm_end(NOTOK);
-           return NOTOK;
-       }
-    }
-#endif /* CYRUS_SASL */
+           /*
+            * If the user asked for SASL, then check to see if the SMTP server
+            * supports it.  Otherwise, error out (because the SMTP server
+            * might have been spoofed; we don't want to just silently not
+            * do authentication
+            */
+
+           if (sasl) {
+               char *server_mechs;
+               if (! (server_mechs = EHLOset("AUTH"))) {
+                   sm_end(NOTOK);
+                   return sm_ierror("SMTP server does not support SASL");
+               }
+               if (netsec_negotiate_sasl(nsc, server_mechs, &errstr) != OK) {
+                   sm_end(NOTOK);
+                   return sm_nerror(errstr);
+               }
+           }
 
            if (watch)
                smtalk (SM_HELO, "VERB on");
@@ -694,10 +542,7 @@ sm_wtxt (char *buffer, int len)
 {
     int result;
 
-    sm_alarmed = 0;
-    alarm (SM_TEXT);
     result = sm_wstream (buffer, len);
-    alarm (0);
 
     return (result == NOTOK ? RP_BHST : RP_OK);
 }
@@ -746,7 +591,7 @@ sm_end (int type)
        }
     }
 
-    if (sm_rfp == NULL && sm_wfp == NULL)
+    if (nsc == NULL)
        return RP_OK;
 
     switch (type) {
@@ -786,36 +631,13 @@ sm_end (int type)
            break;
     }
 
-#ifdef TLS_SUPPORT
-    if (tls_active) {
-       BIO_ssl_shutdown(io);
-       BIO_free_all(io);
-    }
-#endif /* TLS_SUPPORT */
-
-    if (sm_rfp != NULL) {
-       alarm (SM_CLOS);
-       fclose (sm_rfp);
-       alarm (0);
-    }
-    if (sm_wfp != NULL) {
-       alarm (SM_CLOS);
-       fclose (sm_wfp);
-       alarm (0);
+    if (nsc != NULL) {
+       netsec_shutdown(nsc, 1);
+       nsc = NULL;
     }
 
     if (sm_mts == MTS_SMTP) {
        status = 0;
-#ifdef CYRUS_SASL
-       if (conn) {
-           sasl_dispose(&conn);
-           if (sasl_outbuffer) {
-               free(sasl_outbuffer);
-           }
-       }
-       if (sasl_inbuffer)
-           free(sasl_inbuffer);
-#endif /* CYRUS_SASL */
     } else if (sm_child != NOTOK) {
        status = pidwait (sm_child, OK);
        sm_child = NOTOK;
@@ -823,407 +645,37 @@ sm_end (int type)
        status = OK;
     }
 
-    sm_rfp = sm_wfp = NULL;
     return (status ? RP_BHST : RP_OK);
 }
 
-#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.
- */
-
-#define CHECKB64SIZE(insize, outbuf, outsize) \
-    { size_t wantout = (((insize + 2) / 3) * 4) + 32; \
-      if (wantout > outsize) { \
-          outbuf = mh_xrealloc(outbuf, outsize = wantout); \
-      } \
-    }
 
 static int
-sm_auth_sasl(char *user, int saslssf, char *mechlist, char *inhost)
+sm_ierror (const char *fmt, ...)
 {
-    int result, status;
-    unsigned int buflen, outlen;
-    char *buf, *outbuf = NULL, host[NI_MAXHOST];
-    const char *chosen_mech;
-    sasl_security_properties_t secprops;
-    sasl_ssf_t *ssf;
-    int *outbufmax;
-    struct nmh_creds creds = { 0, 0, 0 };
-    size_t outbufsize = 0;
-
-    /*
-     * Initialize the callback contexts
-     */
-
-    /*
-     * 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.
-     */
-
-    memset(host, 0, sizeof(host));
-
-    if (!inhost) {
-       struct sockaddr_storage sin;
-       socklen_t len = sizeof(sin);
-       int result;
-
-       if (getpeername(fileno(sm_wfp), (struct sockaddr *) &sin, &len) < 0) {
-           sm_ierror("getpeername on SMTP socket failed: %s",
-                     strerror(errno));
-           return NOTOK;
-       }
-
-       result = getnameinfo((struct sockaddr *) &sin, len, host, sizeof(host),
-                            NULL, 0, NI_NAMEREQD);
-       if (result != 0) {
-           sm_ierror("Unable to look up name of connected host: %s",
-                     gai_strerror(result));
-           return NOTOK;
-       }
-    } else {
-       strncpy(host, inhost, sizeof(host) - 1);
-    }
-
-    /* It's OK to copy the addresses here.  The callbacks that use
-       them will only be called before this function returns. */
-    creds.host = host;
-    creds.user = user;
-    callbacks[SM_SASL_N_CB_USER].context = &creds;
-    callbacks[SM_SASL_N_CB_AUTHNAME].context = &creds;
-    callbacks[SM_SASL_N_CB_PASS].context = &creds;
-
-    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, NULL, NULL, 0, &conn);
-
-    if (result != SASL_OK) {
-       sm_ierror("SASL client initialization failed: %s",
-                 sasl_errstring(result, NULL, NULL));
-       return NOTOK;
-    }
-
-    /*
-     * Initialize the security properties.  But if TLS is active, then
-     * don't negotiate encryption here.
-     */
-
-    memset(&secprops, 0, sizeof(secprops));
-    secprops.maxbufsize = SASL_MAXRECVBUF;
-    secprops.max_ssf =
-      tls_active ? 0 : (saslssf != -1 ? (unsigned int) saslssf : UINT_MAX);
-
-    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;
-    }
-
-    /*
-     * 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, (const char **) &buf,
-                              &buflen, (const char **) &chosen_mech);
-
-    if (result != SASL_OK && result != SASL_CONTINUE) {
-       sm_ierror("SASL client start failed: %s", sasl_errdetail(conn));
-       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) {
-       CHECKB64SIZE(buflen, outbuf, outbufsize);
-       status = sasl_encode64(buf, buflen, outbuf, outbufsize, NULL);
-       if (status != SASL_OK) {
-           sm_ierror("SASL base64 encode failed: %s",
-                     sasl_errstring(status, NULL, NULL));
-           if (outbuf)
-               free(outbuf);
-           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) {
-           if (outbuf)
-               free(outbuf);
-           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 {
-           if (sm_reply.length > (int) outbufsize) {
-               outbuf = mh_xrealloc(outbuf, outbufsize = sm_reply.length);
-           }
-
-           result = sasl_decode64(sm_reply.text, sm_reply.length,
-                                  outbuf, outbufsize, &outlen);
-           if (result != SASL_OK) {
-               smtalk(SM_AUTH, "*");
-               sm_ierror("SASL base64 decode failed: %s",
-                         sasl_errstring(result, NULL, NULL));
-               if (outbuf)
-                   free(outbuf);
-               return NOTOK;
-           }
-       }
-
-       result = sasl_client_step(conn, outbuf, outlen, NULL,
-                                 (const char **) &buf, &buflen);
-
-       if (result != SASL_OK && result != SASL_CONTINUE) {
-           smtalk(SM_AUTH, "*");
-           sm_ierror("SASL client negotiation failed: %s",
-                     sasl_errstring(result, NULL, NULL));
-           if (outbuf)
-               free(outbuf);
-           return NOTOK;
-       }
-
-       CHECKB64SIZE(buflen, outbuf, outbufsize);
-       status = sasl_encode64(buf, buflen, outbuf, outbufsize, NULL);
-
-       if (status != SASL_OK) {
-           smtalk(SM_AUTH, "*");
-           sm_ierror("SASL base64 encode failed: %s",
-                     sasl_errstring(status, NULL, NULL));
-           if (outbuf)
-               free(outbuf);
-           return NOTOK;
-       }
-       
-       status = smtalk(SM_AUTH, outbuf);
-    }
-
-    if (outbuf)
-       free(outbuf);
-
-    /*
-     * Make sure that we got the correct response
-     */
-
-    if (status < 200 || status > 299)
-       return RP_BHST;
-
-    /*
-     * We _should_ have completed the authentication successfully.
-     * Get a few properties from the authentication exchange.
-     */
-
-    result = sasl_getprop(conn, SASL_MAXOUTBUF, (const 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, (const 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 (sasl_ssf > 0) {
-       sasl_outbuffer = malloc(maxoutbuf);
+    va_list ap;
 
-       if (sasl_outbuffer == NULL) {
-               sm_ierror("Unable to allocate %d bytes for SASL output "
-                         "buffer", maxoutbuf);
-               return NOTOK;
-       }
-       sasl_outbuflen = 0;
-       sasl_inbuflen = 0;
-       sasl_inptr = sasl_inbuffer;
-    } else {
-       sasl_outbuffer = NULL;
-        /* Don't NULL out sasl_inbuffer because it could be used in
-           sm_fgetc (). */
-    }
+    va_start(ap, fmt);
+    vsnprintf (sm_reply.text, sizeof(sm_reply.text), fmt, ap);
+    va_end(ap);
 
-    sasl_complete = 1;
+    sm_reply.length = strlen (sm_reply.text);
+    sm_reply.code = NOTOK;
 
-    return RP_OK;
+    return RP_BHST;
 }
 
 /*
- * Our callback functions to feed data to the SASL library
+ * Like sm_ierror, but assume it's an allocated error string we need to free.
  */
 
 static int
-sm_get_user(void *context, int id, const char **result, unsigned *len)
-{
-    nmh_creds_t creds = (nmh_creds_t) context;
-
-    if (! result || ((id != SASL_CB_USER) && (id != SASL_CB_AUTHNAME)))
-       return SASL_BADPARAM;
-
-    if (creds->user == NULL) {
-        /*
-         * Pass the 1 third argument to nmh_get_credentials() so
-         * that a default user if the -user switch to send(1)/post(8)
-         * wasn't used, and so that a default password will be supplied.
-         * That's used when those values really don't matter, and only
-         * with legacy/.netrc, i.e., with a credentials profile entry.
-         */
-        if (nmh_get_credentials (creds->host, creds->user, 1, creds) != OK) {
-            return SASL_BADPARAM;
-        }
-    }
-
-    *result = creds->user;
-    if (len)
-       *len = strlen(creds->user);
-
-    return SASL_OK;
-}
-
-static int
-sm_get_pass(sasl_conn_t *conn, void *context, int id,
-           sasl_secret_t **psecret)
+sm_nerror (char *str)
 {
-    nmh_creds_t creds = (nmh_creds_t) context;
-    int len;
-
-    NMH_UNUSED (conn);
-
-    if (! psecret || id != SASL_CB_PASS)
-       return SASL_BADPARAM;
-
-    if (creds->password == NULL) {
-        /*
-         * Pass the 0 third argument to nmh_get_credentials() so
-         * that the default password isn't used.  With legacy/.netrc
-         * credentials support, we'll only get here if the -user
-         * switch to send(1)/post(8) wasn't used.
-         */
-        if (nmh_get_credentials (creds->host, creds->user, 0, creds) != OK) {
-            return SASL_BADPARAM;
-        }
-    }
-
-    len = strlen (creds->password);
-
-    if (! (*psecret = (sasl_secret_t *) malloc(sizeof(sasl_secret_t) + len))) {
-       return SASL_NOMEM;
-    }
-
-    (*psecret)->len = len;
-    strcpy((char *) (*psecret)->data, creds->password);
-
-    return SASL_OK;
-}
-#endif /* CYRUS_SASL */
-
-/* https://developers.google.com/gmail/xoauth2_protocol */
-static int
-sm_auth_xoauth2(const char *user, const char *oauth_svc, int snoop)
-{
-    const char *xoauth_client_res;
-    int status;
-
-#ifdef OAUTH_SUPPORT
-    xoauth_client_res = mh_oauth_do_xoauth(user, oauth_svc,
-                                          snoop ? stderr : NULL);
-
-    if (xoauth_client_res == NULL) {
-       return sm_ierror("Internal error: mh_oauth_do_xoauth() returned NULL");
-    }
-#else
-    NMH_UNUSED(user);
-    NMH_UNUSED(snoop);
-    adios(NULL, "sendfrom built without OAUTH_SUPPORT, "
-          "so oauth_svc %s is not supported", oauth_svc);
-#endif /* OAUTH_SUPPORT */
-
-    status = smtalk(SM_AUTH, "AUTH XOAUTH2 %s", xoauth_client_res);
-    if (status == 235) {
-        /* It worked! */
-        return RP_OK;
-    }
-
-    /*
-     * Status is 334 and sm_reply.text contains base64-encoded JSON.  As far as
-     * epg can tell, no matter the error, the JSON is always the same:
-     * {"status":"400","schemes":"Bearer","scope":"https://mail.google.com/"}
-     * I tried these errors:
-     * - garbage token
-     * - expired token
-     * - wrong scope
-     * - wrong username
-     */
-    /* Then we're supposed to send an empty response ("\r\n"). */
-    smtalk(SM_AUTH, "");
-    /*
-     * And now we always get this, again, no matter the error:
-     * 535-5.7.8 Username and Password not accepted. Learn more at
-     * 535 5.7.8 http://support.google.com/mail/bin/answer.py?answer=14257
-     */
-    return RP_BHST;
-}
-
-static int
-sm_ierror (char *fmt, ...)
-{
-    va_list ap;
-
-    va_start(ap, fmt);
-    vsnprintf (sm_reply.text, sizeof(sm_reply.text), fmt, ap);
-    va_end(ap);
-
-    sm_reply.length = strlen (sm_reply.text);
+    strncpy(sm_reply.text, str, sizeof(sm_reply.text));
+    sm_reply.text[sizeof(sm_reply.text) - 1] = '\0';
+    sm_reply.length = strlen(sm_reply.text);
     sm_reply.code = NOTOK;
+    free(str);
 
     return RP_BHST;
 }
@@ -1232,334 +684,91 @@ static int
 smtalk (int time, char *fmt, ...)
 {
     va_list ap;
+    char *errstr;
     int result;
-    char *buffer;
-    size_t bufsize = BUFSIZ;
-
-    buffer = mh_xmalloc(bufsize);
 
     va_start(ap, fmt);
-    result = vsnprintf (buffer, bufsize, fmt, ap);
+    result = netsec_vprintf (nsc, &errstr, fmt, ap);
     va_end(ap);
 
-    if (result > (int) bufsize) {
-       buffer = mh_xrealloc(buffer, bufsize = result + 1);
-       va_start(ap, fmt);
-       vsnprintf (buffer, bufsize, fmt, ap);
-       va_end(ap);
-    }
-
-    if (sm_debug) {
-       char *decoded_buffer =
-            prepare_for_display (buffer, &next_line_encoded);
-
-       if (sasl_ssf)
-               printf("(sasl-encrypted) ");
-       if (tls_active)
-               printf("(tls-encrypted) ");
-       printf ("=> %s\n", decoded_buffer);
-       free (decoded_buffer);
-       fflush (stdout);
-    }
-
-    sm_alarmed = 0;
-    alarm ((unsigned) time);
-    if ((result = sm_wrecord (buffer, strlen (buffer))) != NOTOK)
-       result = smhear ();
-    alarm (0);
-
-    free(buffer);
+    if (result != OK)
+       return sm_nerror(errstr);
 
-    return result;
-}
+    if (netsec_printf (nsc, &errstr, "\r\n") != OK)
+       return sm_nerror(errstr);
 
+    if (netsec_flush (nsc, &errstr) != OK)
+       return sm_nerror(errstr);
 
-/*
- * write the buffer to the open SMTP channel
- */
+    netsec_set_timeout(nsc, time);
 
-static int
-sm_wrecord (char *buffer, int len)
-{
-    if (sm_wfp == NULL)
-       return sm_werror ();
-
-    sm_fwrite (buffer, len);
-    sm_fputs ("\r\n");
-    sm_fflush ();
-
-    return (ferror (sm_wfp) ? sm_werror () : OK);
+    return smhear ();
 }
 
 
 static int
 sm_wstream (char *buffer, int len)
 {
-    char  *bp;
+    char *bp, *errstr;
     static char lc = '\0';
+    int rc;
 
-    if (sm_wfp == NULL)
-       return sm_werror ();
+    if (nsc == NULL) {
+       sm_ierror("No socket opened");
+       return NOTOK;
+    }
 
     if (buffer == NULL && len == 0) {
+       rc = OK;
        if (lc != '\n')
-           sm_fputs ("\r\n");
+           rc = netsec_write(nsc, "\r\n", 2, &errstr);
        lc = '\0';
-       return (ferror (sm_wfp) ? sm_werror () : OK);
+       if (rc != OK)
+           sm_nerror(errstr);
+       return rc;
     }
 
     for (bp = buffer; bp && len > 0; bp++, len--) {
        switch (*bp) {
            case '\n': 
                sm_nl = TRUE;
-               sm_fputc ('\r');
+               if (netsec_write(nsc, "\r", 1, &errstr) != OK) {
+                   sm_nerror(errstr);
+                   return NOTOK;
+               }
                break;
 
            case '.': 
                if (sm_nl)
-                   sm_fputc ('.');/* FALL THROUGH */
+                   if (netsec_write(nsc, ".", 1, &errstr) != OK) {
+                   sm_nerror(errstr);
+                   return NOTOK;
+               } /* FALL THROUGH */
+
            default: 
                sm_nl = FALSE;
        }
-       sm_fputc (*bp);
-       if (ferror (sm_wfp))
-           return sm_werror ();
+       if (netsec_write(nsc, bp, 1, &errstr) != OK) {
+           sm_nerror(errstr);
+           return NOTOK;
+       }
     }
 
     if (bp > buffer)
        lc = *--bp;
-    return (ferror (sm_wfp) ? sm_werror () : OK);
-}
-
-/*
- * Write out to the network, but do buffering for SASL (if enabled)
- */
-
-static int
-sm_fwrite(char *buffer, int len)
-{
-#ifdef CYRUS_SASL
-    const char *output;
-    unsigned int outputlen;
-
-    if (sasl_complete == 0 || sasl_ssf == 0) {
-#endif /* CYRUS_SASL */
-#ifdef TLS_SUPPORT
-       if (tls_active) {
-           int ret;
-
-           ret = BIO_write(io, buffer, len);
-
-           if (ret <= 0) {
-               sm_ierror("TLS error during write: %s",
-                         ERR_error_string(ERR_get_error(), NULL));
-               return NOTOK;
-           }
-       } else
-#endif /* TLS_SUPPORT */
-       if ((int) fwrite(buffer, sizeof(*buffer), len, sm_wfp) < len) {
-           advise ("sm_fwrite", "fwrite");
-       }
-#ifdef CYRUS_SASL
-    } else {
-       while (len >= maxoutbuf - sasl_outbuflen) {
-           memcpy(sasl_outbuffer + sasl_outbuflen, buffer,
-                  maxoutbuf - sasl_outbuflen);
-           len -= maxoutbuf - sasl_outbuflen;
-           sasl_outbuflen = 0;
-
-           if (sasl_encode(conn, sasl_outbuffer, maxoutbuf,
-                           &output, &outputlen) != SASL_OK) {
-               sm_ierror("Unable to SASL encode connection data: %s",
-                         sasl_errdetail(conn));
-               return NOTOK;
-           }
-
-           if (fwrite(output, sizeof(*output), outputlen, sm_wfp) <
-               outputlen) {
-               advise ("sm_fwrite", "fwrite");
-           }
-       }
-
-       if (len > 0) {
-           memcpy(sasl_outbuffer + sasl_outbuflen, buffer, len);
-           sasl_outbuflen += len;
-       }
-    }
-#endif /* CYRUS_SASL */
-    return ferror(sm_wfp) ? NOTOK : RP_OK;
-}
-
-#ifdef TLS_SUPPORT
-/*
- * Negotiate Transport Layer Security
- */
-
-static int
-tls_negotiate(void)
-{
-    BIO *ssl_bio;
-
-    if (! sslctx) {
-       const SSL_METHOD *method;
-
-       SSL_library_init();
-       SSL_load_error_strings();
-
-       method = TLSv1_client_method(); /* Not sure about this */
-
-       /* Older ssl takes a non-const arg. */
-       sslctx = SSL_CTX_new((SSL_METHOD *) method);
-
-       if (! sslctx) {
-           sm_end(NOTOK);
-           return sm_ierror("Unable to initialize OpenSSL context: %s",
-                            ERR_error_string(ERR_get_error(), NULL));
-       }
-    }
-
-    ssl = SSL_new(sslctx);
-
-    if (! ssl) {
-       sm_end(NOTOK);
-       return sm_ierror("Unable to create SSL connection: %s",
-                        ERR_error_string(ERR_get_error(), NULL));
-    }
-
-    sbior = BIO_new_socket(fileno(sm_rfp), BIO_NOCLOSE);
-    sbiow = BIO_new_socket(fileno(sm_wfp), BIO_NOCLOSE);
-
-    if (sbior == NULL || sbiow == NULL) {
-       sm_end(NOTOK);
-       return sm_ierror("Unable to create BIO endpoints: %s",
-                        ERR_error_string(ERR_get_error(), NULL));
-    }
-
-    SSL_set_bio(ssl, sbior, sbiow);
-    SSL_set_connect_state(ssl);
-
-    /*
-     * Set up a BIO to handle buffering for us
-     */
-
-    io = BIO_new(BIO_f_buffer());
-
-    if (! io) {
-       sm_end(NOTOK);
-       return sm_ierror("Unable to create a buffer BIO: %s",
-                        ERR_error_string(ERR_get_error(), NULL));
-    }
-
-    ssl_bio = BIO_new(BIO_f_ssl());
-
-    if (! ssl_bio) {
-       sm_end(NOTOK);
-       return sm_ierror("Unable to create a SSL BIO: %s",
-                        ERR_error_string(ERR_get_error(), NULL));
-    }
-
-    BIO_set_ssl(ssl_bio, ssl, BIO_CLOSE);
-    BIO_push(io, ssl_bio);
-
-    /*
-     * Try doing the handshake now
-     */
-
-    if (BIO_do_handshake(io) < 1) {
-       sm_end(NOTOK);
-       return sm_ierror("Unable to negotiate SSL connection: %s",
-                        ERR_error_string(ERR_get_error(), NULL));
-    }
-
-    if (sm_debug) {
-       const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl);
-       printf("SSL negotiation successful: %s(%d) %s\n",
-              SSL_CIPHER_get_name(cipher),
-              SSL_CIPHER_get_bits(cipher, NULL),
-              SSL_CIPHER_get_version(cipher));
-
-    }
-
-    tls_active = 1;
-
-    return RP_OK;
-}
-#endif /* TLS_SUPPORT */
-
-/*
- * Convenience functions to replace occurrences of fputs() and fputc()
- */
-
-static int
-sm_fputs(char *buffer)
-{
-    return sm_fwrite(buffer, strlen(buffer));
-}
-
-static int
-sm_fputc(int c)
-{
-    char h = c;
-
-    return sm_fwrite(&h, 1);
-}
-
-/*
- * Flush out any pending data on the connection
- */
-
-static void
-sm_fflush(void)
-{
-#ifdef CYRUS_SASL
-    const char *output;
-    unsigned int outputlen;
-    int result;
-
-    if (sasl_complete == 1 && sasl_ssf > 0 && sasl_outbuflen > 0) {
-       result = sasl_encode(conn, sasl_outbuffer, sasl_outbuflen,
-                            &output, &outputlen);
-       if (result != SASL_OK) {
-           sm_ierror("Unable to SASL encode connection data: %s",
-                     sasl_errdetail(conn));
-           return;
-       }
-
-       if (fwrite(output, sizeof(*output), outputlen, sm_wfp) < outputlen) {
-           advise ("sm_fflush", "fwrite");
-       }
-       sasl_outbuflen = 0;
-    }
-#endif /* CYRUS_SASL */
-
-#ifdef TLS_SUPPORT
-    if (tls_active) {
-       (void) BIO_flush(io);
-    }
-#endif /* TLS_SUPPORT */
-
-    fflush(sm_wfp);
-}
-
-static int
-sm_werror (void)
-{
-    sm_reply.length =
-       strlen (strcpy (sm_reply.text, sm_wfp == NULL ? "no socket opened"
-           : sm_alarmed ? "write to socket timed out"
-           : "error writing to socket"));
-
-    return (sm_reply.code = NOTOK);
+    return OK;
 }
 
 
 static int
 smhear (void)
 {
-    int i, code, cont, bc = 0, rc, more;
+    int i, code, cont, more;
+    size_t buflen, rc;
     unsigned char *bp;
     char *rp;
-    char **ehlo = EHLOkeys, buffer[BUFSIZ];
+    char *errstr;
+    char **ehlo = EHLOkeys, *buffer;
 
     if (doingEHLO) {
        static int at_least_once = 0;
@@ -1586,20 +795,8 @@ again: ;
     rp = sm_reply.text;
     rc = sizeof(sm_reply.text) - 1;
 
-    for (more = FALSE; sm_rrecord ((char *) (bp = (unsigned char *) buffer),
-                                  &bc) != NOTOK ; ) {
-       if (sm_debug) {
-           char *decoded_buffer =
-                prepare_for_display (buffer, &next_line_encoded);
-
-           if (sasl_ssf > 0)
-               printf("(sasl-decrypted) ");
-           if (tls_active)
-               printf("(tls-decrypted) ");
-           printf ("<= %s\n", decoded_buffer);
-           free (decoded_buffer);
-           fflush (stdout);
-       }
+    for (more = FALSE; (buffer = netsec_readline(nsc, &buflen,
+                                                &errstr)) != NULL ; ) {
 
        if (doingEHLO
                && strncmp (buffer, "250", sizeof("250") - 1) == 0
@@ -1619,18 +816,20 @@ again: ;
                doingEHLO = 2;
        }
 
-       for (; bc > 0 && (!isascii (*bp) || !isdigit (*bp)); bp++, bc--)
+       bp = (unsigned char *) buffer;
+
+       for (; buflen > 0 && (!isascii (*bp) || !isdigit (*bp)); bp++, buflen--)
            continue;
 
        cont = FALSE;
        code = atoi ((char *) bp);
-       bp += 3, bc -= 3;
-       for (; bc > 0 && isspace (*bp); bp++, bc--)
+       bp += 3, buflen -= 3;
+       for (; buflen > 0 && isspace (*bp); bp++, buflen--)
            continue;
-       if (bc > 0 && *bp == '-') {
+       if (buflen > 0 && *bp == '-') {
            cont = TRUE;
-           bp++, bc--;
-           for (; bc > 0 && isspace (*bp); bp++, bc--)
+           bp++, buflen--;
+           for (; buflen > 0 && isspace (*bp); bp++, buflen--)
                continue;
        }
 
@@ -1641,20 +840,18 @@ again: ;
        } else {
            sm_reply.code = code;
            more = cont;
-           if (bc <= 0) {
-               /* can never fail to 0-terminate because of size of buffer vs fixed string */
-               strncpy (buffer, sm_noreply, sizeof(buffer));
-               bp = (unsigned char *) buffer;
-               bc = strlen (sm_noreply);
+           if (buflen <= 0) {
+               bp = (unsigned char *) sm_noreply;
+               buflen = strlen (sm_noreply);
            }
        }
 
-       if ((i = min (bc, rc)) > 0) {
+       if ((i = min (buflen, rc)) > 0) {
            memcpy (rp, bp, i);
            rp += i;
            rc -= i;
            i = strlen(sm_moreply);
-           if (more && rc > i + 1) {
+           if (more && (int) rc > i + 1) {
                memcpy (rp, sm_moreply, i); /* safe because of check in if() */
                rp += i;
                rc -= i;
@@ -1674,198 +871,9 @@ again: ;
        sm_reply.text[sm_reply.length] = 0;
        return sm_reply.code;
     }
-    return NOTOK;
-}
-
-
-static int
-sm_rrecord (char *buffer, int *len)
-{
-    int retval;
-
-    if (sm_rfp == NULL)
-       return sm_rerror(0);
-
-    buffer[*len = 0] = 0;
-
-    if ((retval = sm_fgets (buffer, BUFSIZ, sm_rfp)) != RP_OK)
-       return sm_rerror (retval);
-    *len = strlen (buffer);
-    /* *len should be >0 except on EOF, but check for safety's sake */
-    if (*len == 0)
-       return sm_rerror (RP_EOF);
-    if (buffer[*len - 1] != '\n')
-       while ((retval = sm_fgetc (sm_rfp)) != '\n' && retval != EOF &&
-              retval != -2)
-           continue;
-    else
-       if ((*len > 1) && (buffer[*len - 2] == '\r'))
-           *len -= 1;
-    *len -= 1;
-    buffer[*len] = 0;
-
-    return OK;
-}
-
-/*
- * Our version of fgets, which calls our private fgetc function
- */
-
-static int
-sm_fgets(char *buffer, int size, FILE *f)
-{
-    int c;
-
-     do {
-       c = sm_fgetc(f);
-
-       if (c == EOF)
-           return RP_EOF;
-
-       if (c == -2)
-           return NOTOK;
 
-       *buffer++ = c;
-     } while (size > 1 && c != '\n');
-
-     *buffer = '\0';
-
-     return RP_OK;
-}
-
-
-#if defined(CYRUS_SASL) || defined(TLS_SUPPORT)
-/*
- * Read from the network, but do SASL or TLS encryption
- */
-
-static int
-sm_fgetc(FILE *f)
-{
-    char tmpbuf[BUFSIZ], *retbuf;
-    unsigned int retbufsize = 0;
-    int cc, result;
-
-    /*
-     * If we have leftover data, return it
-     */
-
-    if (sasl_inbuflen) {
-       sasl_inbuflen--;
-       return (int) *sasl_inptr++;
-    }
-
-    /*
-     * If not, read from the network until we have some data to return
-     */
-
-    while (retbufsize == 0) {
-
-#ifdef TLS_SUPPORT
-       if (tls_active) {
-           cc = SSL_read(ssl, tmpbuf, sizeof(tmpbuf));
-
-           if (cc == 0) {
-               result = SSL_get_error(ssl, cc);
-
-               if (result != SSL_ERROR_ZERO_RETURN) {
-                   sm_ierror("TLS peer aborted connection");
-               }
-
-               return EOF;
-           }
-
-           if (cc < 0) {
-               sm_ierror("SSL_read failed: %s",
-                         ERR_error_string(ERR_get_error(), NULL));
-               return -2;
-           }
-       } else
-#endif /* TLS_SUPPORT */
-
-       cc = read(fileno(f), tmpbuf, sizeof(tmpbuf));
-
-       if (cc == 0)
-           return EOF;
-
-       if (cc < 0) {
-           sm_ierror("Unable to read from network: %s", strerror(errno));
-           return -2;
-       }
-
-       /*
-        * Don't call sasl_decode unless sasl is complete and we have
-        * encryption working
-        */
-
-#ifdef CYRUS_SASL
-       if (sasl_complete == 0 || sasl_ssf == 0) {
-           retbuf = tmpbuf;
-           retbufsize = cc;
-       } else {
-           result = sasl_decode(conn, tmpbuf, cc, (const char **) &retbuf,
-                                &retbufsize);
-
-           if (result != SASL_OK) {
-               sm_ierror("Unable to decode SASL network data: %s",
-                         sasl_errdetail(conn));
-               return -2;
-           }
-       }
-#else /* ! CYRUS_SASL */
-       retbuf = tmpbuf;
-       retbufsize = cc;
-#endif /* CYRUS_SASL */
-    }
-
-    if (retbufsize > SASL_MAXRECVBUF) {
-       sm_ierror("Received data (%d bytes) is larger than the buffer "
-                 "size (%d bytes)", retbufsize, SASL_MAXRECVBUF);
-       return -2;
-    }
-
-    memcpy(sasl_inbuffer, retbuf, retbufsize);
-    sasl_inptr = sasl_inbuffer + 1;
-    sasl_inbuflen = retbufsize - 1;
-
-    return (int) sasl_inbuffer[0];
-}
-#endif /* CYRUS_SASL || TLS_SUPPORT */
-
-static int
-sm_rerror (int rc)
-{
-    if (sm_mts == MTS_SMTP)
-       sm_reply.length =
-           strlen (strcpy (sm_reply.text, sm_rfp == NULL ? "no socket opened"
-               : sm_alarmed ? "read from socket timed out"
-               : rc == RP_EOF ? "premature end-of-file on socket"
-               : "error reading from socket"));
-    else
-       sm_reply.length =
-           strlen (strcpy (sm_reply.text, sm_rfp == NULL ? "no pipe opened"
-               : sm_alarmed ? "read from pipe timed out"
-               : rc == RP_EOF ? "premature end-of-file on pipe"
-               : "error reading from pipe"));
-
-    return (sm_reply.code = NOTOK);
-}
-
-
-static void
-alrmser (int i)
-{
-    NMH_UNUSED (i);
-
-#ifndef        RELIABLE_SIGNALS
-    SIGNAL (SIGALRM, alrmser);
-#endif
-
-    sm_alarmed++;
-    if (sm_debug) {
-       printf ("timed out...\n");
-       fflush (stdout);
-    }
+    sm_nerror(errstr);
+    return NOTOK;
 }
 
 
@@ -1940,68 +948,143 @@ EHLOset (char *s)
     return 0;
 }
 
-
 /*
- * Detects, using heuristics, if an SMTP server or client response string
- * contains a base64-encoded portion.  If it does, decodes it and replaces
- * any non-printable characters with a hex representation.  Caller is
- * responsible for free'ing return value.  If the decode fails, a copy of
- * the input string is returned.
+ * Our SASL callback; we are either given SASL tokens to generate network
+ * protocols messages for, or we decode incoming protocol messages and
+ * convert them to binary SASL tokens to pass up into the SASL library.
  */
-static
-char *
-prepare_for_display (const char *string, int *next_line_encoded) {
-    const char *start = NULL;
-    const char *decoded;
-    size_t decoded_len;
-    int prefix_len = -1;
-
-    if (strncmp (string, "AUTH ", 5) == 0) {
-        /* AUTH line:  the mechanism isn't encoded.  If there's an initial
-           response, it must be base64 encoded.. */
-        char *mechanism = strchr (string + 5, ' ');
-
-        if (mechanism != NULL) {
-            prefix_len = (int) (mechanism - string + 1);
-        } /* else no space following the mechanism, so no initial response */
-        *next_line_encoded = 0;
-    } else if (strncmp (string, "334 ", 4) == 0) {
-        /* 334 is the server's request for user or password. */
-        prefix_len = 4;
-        /* The next (client response) line must be base64 encoded. */
-        *next_line_encoded = 1;
-    } else if (*next_line_encoded) {
-        /* "next" line now refers to this line, which is a base64-encoded
-           client response. */
-        prefix_len = 0;
-        *next_line_encoded = 0;
-    } else {
-        *next_line_encoded = 0;
-    }
 
-    /* Don't attempt to decoded unencoded initial response ('=') or cancel
-       response ('*'). */
-    if (prefix_len > -1  &&
-        string[prefix_len] != '='  &&  string[prefix_len] != '*') {
-        start = string + prefix_len;
-    }
+static int
+sm_sasl_callback(enum sasl_message_type mtype, unsigned const char *indata,
+                unsigned int indatalen, unsigned char **outdata,
+                unsigned int *outdatalen, char **errstr)
+{
+    int rc, snoopoffset;
+    char *mech, *line;
+    size_t len;
 
-    if (start  &&  decodeBase64 (start, &decoded, &decoded_len, 1, NULL) == OK) {
-        char *hexified;
-        char *prefix = mh_xmalloc(prefix_len + 1);
-        char *display_string;
-
-        /* prefix is the beginning portion, which isn't base64 encoded. */
-        snprintf (prefix, prefix_len + 1, "%*s", prefix_len, string);
-        hexify ((const unsigned char *) decoded, decoded_len, &hexified);
-        /* Wrap the decoded portion in "b64<>". */
-        display_string = concat (prefix, "b64<", hexified, ">", NULL);
-        free (hexified);
-        free (prefix);
-        free ((char *) decoded);
-
-        return display_string;
-    } else {
-        return getcpy (string);
+    switch (mtype) {
+    case NETSEC_SASL_START:
+       /*
+        * Generate an AUTH message; if we were given an input token
+        * then generate a an AUTH message that includes the initial
+        * response.
+        */
+
+       mech = netsec_get_sasl_mechanism(nsc);
+
+       if (indatalen) {
+           char *b64data;
+
+           b64data = mh_xmalloc(BASE64SIZE(indatalen));
+           writeBase64raw(indata, indatalen, (unsigned char *) b64data);
+
+           netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder,
+                                     &snoopoffset);
+           snoopoffset = 6 + strlen(mech);
+           rc = netsec_printf(nsc, errstr, "AUTH %s %s\r\n", mech, b64data);
+           free(b64data);
+           netsec_set_snoop_callback(nsc, NULL, NULL);
+       } else {
+           rc = netsec_printf(nsc, errstr, "AUTH %s\r\n", mech);
+       }
+
+       if (rc != OK)
+           return NOTOK;
+
+       if (netsec_flush(nsc, errstr) != OK)
+           return NOTOK;
+
+       break;
+
+    case NETSEC_SASL_READ:
+       /*
+        * Read in a line that should contain a 334 response code, followed
+        * by base64 response data.
+        */
+
+       netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, &snoopoffset);
+       snoopoffset = 4;
+       line = netsec_readline(nsc, &len, errstr);
+       netsec_set_snoop_callback(nsc, NULL, NULL);
+
+       if (line == NULL)
+           return NOTOK;
+
+       if (len < 4) {
+           netsec_err(errstr, "Invalid format for SASL response");
+           return NOTOK;
+       }
+
+       if (strncmp(line, "334 ", 4) != 0) {
+           netsec_err(errstr, "Improper SASL protocol response: %s", line);
+           return NOTOK;
+       }
+
+       if (len == 4) {
+           *outdata = NULL;
+           *outdatalen = 0;
+       } else {
+           rc = decodeBase64(line + 4, outdata, &len, 0, NULL);
+           if (rc != OK) {
+               netsec_err(errstr, "Unable to decode base64 response");
+               return NOTOK;
+           }
+           *outdatalen = len;
+       }
+       break;
+
+    case NETSEC_SASL_WRITE:
+       /*
+        * The output encoding is pretty simple, so this is easy.
+        */
+       if (indatalen == 0) {
+           rc = netsec_printf(nsc, errstr, "\r\n");
+       } else {
+           unsigned char *b64data;
+           b64data = mh_xmalloc(BASE64SIZE(indatalen));
+           writeBase64raw(indata, indatalen, b64data);
+           netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, NULL);
+           rc = netsec_printf(nsc, errstr, "%s\r\n", b64data);
+           netsec_set_snoop_callback(nsc, NULL, NULL);
+           free(b64data);
+       }
+
+       if (rc != OK)
+           return NOTOK;
+
+       if (netsec_flush(nsc, errstr) != OK)
+           return NOTOK;
+       break;
+
+    case NETSEC_SASL_FINISH:
+       /*
+        * Finish the protocol; we're looking for a 235 message.
+        */
+       line = netsec_readline(nsc, &len, errstr);
+       if (line == NULL)
+           return NOTOK;
+
+       if (strncmp(line, "235 ", 4) != 0) {
+           if (len > 4)
+               netsec_err(errstr, "Authentication failed: %s", line + 4);
+           else
+               netsec_err(errstr, "Authentication failed: %s", line);
+           return NOTOK;
+       }
+       break;
+
+    case NETSEC_SASL_CANCEL:
+       /*
+        * Cancel the SASL exchange; this is done by sending a single "*".
+        */
+       rc = netsec_printf(nsc, errstr, "*\r\n");
+       if (rc == OK)
+           rc = netsec_flush(nsc, errstr);
+       if (rc != OK)
+           return NOTOK;
+       break;
     }
+
+    return OK;
 }
index 3063866..fb842fe 100644 (file)
@@ -16,8 +16,8 @@ struct smtp {
  * prototypes
  */
 /* int client (); */
-int sm_init (char *, char *, char *, int, int, int, int, int, char *, char *,
-             const char *, int);
+int sm_init (char *, char *, char *, int, int, int, int, const char *,
+             const char *, const char *, int);
 int sm_winit (char *, int);
 int sm_wadr (char *, char *, char *);
 int sm_waend (void);
index eb3c8ea..79d7aa9 100644 (file)
@@ -113,7 +113,7 @@ writeBase64aux (FILE *in, FILE *out, int crlf)
      4 * [length/3]  +  length/57  +  2
    But double the length will certainly be sufficient. */
 int
-writeBase64 (unsigned char *in, size_t length, unsigned char *out)
+writeBase64 (const unsigned char *in, size_t length, unsigned char *out)
 {
     unsigned int n = BPERLIN;
 
@@ -121,7 +121,7 @@ writeBase64 (unsigned char *in, size_t length, unsigned char *out)
        unsigned long bits;
        unsigned char *bp;
        unsigned int cc;
-       for (cc = 0, bp = in; length > 0 && cc < 3; ++cc, ++bp, --length)
+       for (cc = 0; length > 0 && cc < 3; ++cc, --length)
           /* empty */ ;
 
        if (cc == 0) {
@@ -168,13 +168,13 @@ writeBase64 (unsigned char *in, size_t length, unsigned char *out)
  */
 
 int
-writeBase64raw (unsigned char *in, size_t length, unsigned char *out)
+writeBase64raw (const unsigned char *in, size_t length, unsigned char *out)
 {
     while (1) {
        unsigned long bits;
        unsigned char *bp;
        unsigned int cc;
-       for (cc = 0, bp = in; length > 0 && cc < 3; ++cc, ++bp, --length)
+       for (cc = 0; length > 0 && cc < 3; ++cc, --length)
           /* empty */ ;
 
        if (cc == 0) {
@@ -233,8 +233,8 @@ static unsigned char b642nib[0x80] = {
  * See description of arguments with declaration in h/prototypes.h.
  */
 int
-decodeBase64 (const char *encoded, const char **decoded, size_t *len, int skip_crs,
-              unsigned char *digest) {
+decodeBase64 (const char *encoded, unsigned char **decoded, size_t *len,
+             int skip_crs, unsigned char *digest) {
     const char *cp = encoded;
     int self_delimiting = 0;
     int bitno, skip;
@@ -314,7 +314,7 @@ test_end:
         return NOTOK;
     }
 
-    *decoded = charstring_buffer_copy (decoded_c);
+    *decoded = (unsigned char *) charstring_buffer_copy (decoded_c);
     *len = charstring_bytes (decoded_c);
     charstring_free (decoded_c);
 
diff --git a/sbr/netsec.c b/sbr/netsec.c
new file mode 100644 (file)
index 0000000..97d306b
--- /dev/null
@@ -0,0 +1,1605 @@
+
+/*
+ * netsec.c -- Network security routines for handling protocols that
+ *            require SASL and/or TLS.
+ *
+ * This code is Copyright (c) 2016, 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>
+#include <h/netsec.h>
+#include <h/oauth.h>
+#include <stdarg.h>
+#include <sys/select.h>
+
+#ifdef CYRUS_SASL
+#include <sasl/sasl.h>
+#include <sasl/saslutil.h>
+# if SASL_VERSION_FULL < 0x020125
+  /* Cyrus SASL 2.1.25 introduced the sasl_callback_ft prototype,
+     which has an explicit void parameter list, according to best
+     practice.  So we need to cast to avoid compile warnings.
+     Provide this prototype for earlier versions. */
+  typedef int (*sasl_callback_ft)();
+# endif /* SASL_VERSION_FULL < 0x020125 */
+
+static int netsec_get_user(void *context, int id, const char **result,
+                          unsigned int *len);
+static int netsec_get_password(sasl_conn_t *conn, void *context, int id,
+                              sasl_secret_t **psecret);
+
+static int sasl_initialized = 0;
+
+#define SASL_MAXRECVBUF 65536
+#endif /* CYRUS_SASL */
+
+#ifdef TLS_SUPPORT
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+static int tls_initialized = 0;
+static SSL_CTX *sslctx = NULL;         /* SSL Context */
+
+#endif /* TLS_SUPPORT */
+
+/* I'm going to hardcode this for now; maybe make it adjustable later? */
+#define NETSEC_BUFSIZE 65536
+
+/*
+ * Our context structure, which holds all of the relevant information
+ * about a connection.
+ */
+
+struct _netsec_context {
+    int ns_readfd;             /* Read descriptor for network connection */
+    int ns_writefd;            /* Write descriptor for network connection */
+    int ns_snoop;              /* If true, display network data */
+    int ns_snoop_noend;                /* If true, didn't get a CR/LF on last line */
+    netsec_snoop_callback *ns_snoop_cb; /* Snoop output callback */
+    void *ns_snoop_context;    /* Context data for snoop function */
+    int ns_timeout;            /* Network read timeout, in seconds */
+    char *ns_userid;           /* Userid for authentication */
+    unsigned char *ns_inbuffer;        /* Our read input buffer */
+    unsigned char *ns_inptr;   /* Our read buffer input pointer */
+    unsigned int ns_inbuflen;  /* Length of data in input buffer */
+    unsigned int ns_inbufsize; /* Size of input buffer */
+    unsigned char *ns_outbuffer;/* Output buffer */
+    unsigned char *ns_outptr;  /* Output buffer pointer */
+    unsigned int ns_outbuflen; /* Output buffer data length */
+    unsigned int ns_outbufsize;        /* Output buffer size */
+    char *sasl_mech;           /* User-requested mechanism */
+    char *sasl_chosen_mech;    /* Mechanism chosen by SASL */
+    netsec_sasl_callback sasl_proto_cb; /* SASL callback we use */
+#ifdef OAUTH_SUPPORT
+    char *oauth_service;       /* OAuth2 service name */
+#endif /* OAUTH_SUPPORT */
+#ifdef CYRUS_SASL
+    char *sasl_hostname;       /* Hostname we've connected to */
+    sasl_conn_t *sasl_conn;    /* SASL connection context */
+    sasl_ssf_t sasl_ssf;       /* SASL Security Strength Factor */
+    sasl_callback_t *sasl_cbs; /* Callbacks used by SASL */
+    nmh_creds_t sasl_creds;    /* Credentials (username/password) */
+    sasl_secret_t *sasl_secret;        /* SASL password structure */
+    int sasl_seclayer;         /* If true, SASL security layer is enabled */
+    char *sasl_tmpbuf;         /* Temporary read buffer for decodes */
+    size_t sasl_maxbufsize;    /* Maximum negotiated SASL buffer size */
+#endif /* CYRUS_SASL */
+#ifdef TLS_SUPPORT
+    BIO *ssl_io;               /* BIO used for connection I/O */
+    int tls_active;            /* If true, TLS is running */
+#endif /* TLS_SUPPORT */
+};
+
+/*
+ * Function to read data from the actual network socket
+ */
+
+static int netsec_fillread(netsec_context *ns_context, char **errstr);
+
+/*
+ * Code to check the ASCII content of a byte array.
+ */
+
+static int checkascii(const unsigned char *byte, size_t len);
+
+/*
+ * How this code works, in general.
+ *
+ * _If_ we are using no encryption or SASL encryption, then we buffer the
+ * network data through ns_inbuffer and ns_outbuffer.  That should be
+ * relatively self-explanatory.
+ *
+ * If we are using SSL for encryption, then use a buffering BIO for output
+ * (that just easier).  Still do buffering for reads; when we need more
+ * data we call the BIO_read() function to fill our local buffer.
+ *
+ * For SASL, we make use of (for now) the Cyrus-SASL library.  For some
+ * mechanisms, we implement those mechanisms directly since the Cyrus SASL
+ * library doesn't support them (like OAuth).
+ */
+
+/*
+ * Allocate and initialize our security context
+ */
+
+netsec_context *
+netsec_init(void)
+{
+    netsec_context *nsc = mh_xmalloc(sizeof(*nsc));
+
+    nsc->ns_readfd = -1;
+    nsc->ns_writefd = -1;
+    nsc->ns_snoop = 0;
+    nsc->ns_snoop_noend = 0;
+    nsc->ns_snoop_cb = NULL;
+    nsc->ns_snoop_context = NULL;
+    nsc->ns_userid = NULL;
+    nsc->ns_timeout = 60;      /* Our default */
+    nsc->ns_inbufsize = NETSEC_BUFSIZE;
+    nsc->ns_inbuffer = mh_xmalloc(nsc->ns_inbufsize);
+    nsc->ns_inptr = nsc->ns_inbuffer;
+    nsc->ns_inbuflen = 0;
+    nsc->ns_outbufsize = NETSEC_BUFSIZE;
+    nsc->ns_outbuffer = mh_xmalloc(nsc->ns_outbufsize);
+    nsc->ns_outptr = nsc->ns_outbuffer;
+    nsc->ns_outbuflen = 0;
+    nsc->sasl_mech = NULL;
+    nsc->sasl_chosen_mech = NULL;
+    nsc->sasl_proto_cb = NULL;
+#ifdef OAUTH_SUPPORT
+    nsc->oauth_service = NULL;
+#endif /* OAUTH_SUPPORT */
+#ifdef CYRUS_SASL
+    nsc->sasl_conn = NULL;
+    nsc->sasl_hostname = NULL;
+    nsc->sasl_cbs = NULL;
+    nsc->sasl_creds = NULL;
+    nsc->sasl_secret = NULL;
+    nsc->sasl_ssf = 0;
+    nsc->sasl_seclayer = 0;
+    nsc->sasl_tmpbuf = NULL;
+    nsc->sasl_maxbufsize = 0;
+#endif /* CYRUS_SASL */
+#ifdef TLS_SUPPORT
+    nsc->ssl_io = NULL;
+    nsc->tls_active = 0;
+#endif /* TLS_SUPPORT */
+    return nsc;
+}
+
+/*
+ * Shutdown the connection completely and free all resources.
+ * The connection is only closed if the flag is given.
+ */
+
+void
+netsec_shutdown(netsec_context *nsc, int closeflag)
+{
+    if (nsc->ns_userid)
+       free(nsc->ns_userid);
+    if (nsc->ns_inbuffer)
+       free(nsc->ns_inbuffer);
+    if (nsc->ns_outbuffer)
+       free(nsc->ns_outbuffer);
+    if (nsc->sasl_mech)
+       free(nsc->sasl_mech);
+    if (nsc->sasl_chosen_mech)
+       free(nsc->sasl_chosen_mech);
+#ifdef OAUTH_SERVICE
+    if (nsc->oauth_service)
+       free(nsc->oauth_service);
+#endif /* OAUTH_SERVICE */
+#ifdef CYRUS_SASL
+    if (nsc->sasl_conn)
+       sasl_dispose(&nsc->sasl_conn);
+    if (nsc->sasl_hostname)
+       free(nsc->sasl_hostname);
+    if (nsc->sasl_cbs)
+       free(nsc->sasl_cbs);
+    if (nsc->sasl_creds) {
+       if (nsc->sasl_creds->password)
+           memset(nsc->sasl_creds->password, 0,
+                  strlen(nsc->sasl_creds->password));
+       free(nsc->sasl_creds);
+    }
+    if (nsc->sasl_secret) {
+       if (nsc->sasl_secret->len > 0) {
+           memset(nsc->sasl_secret->data, 0, nsc->sasl_secret->len);
+       }
+       free(nsc->sasl_secret);
+    }
+    if (nsc->sasl_tmpbuf)
+       free(nsc->sasl_tmpbuf);
+#endif /* CYRUS_SASL */
+#ifdef TLS_SUPPORT
+    if (nsc->ssl_io)
+       /*
+        * I checked; BIO_free_all() will cause SSL_shutdown to be called
+        * on the SSL object in the chain.
+        */
+       BIO_free_all(nsc->ssl_io);
+#endif /* TLS_SUPPORT */
+
+    if (closeflag) {
+       if (nsc->ns_readfd != -1)
+           close(nsc->ns_readfd);
+       if (nsc->ns_writefd != -1 && nsc->ns_writefd != nsc->ns_readfd)
+           close(nsc->ns_writefd);
+    }
+
+    free(nsc);
+}
+
+/*
+ * Set the file descriptor for our context
+ */
+
+void
+netsec_set_fd(netsec_context *nsc, int readfd, int writefd)
+{
+    nsc->ns_readfd = readfd;
+    nsc->ns_writefd = writefd;
+}
+
+/*
+ * Set the userid used for authentication for this context
+ */
+
+void
+netsec_set_userid(netsec_context *nsc, const char *userid)
+{
+    nsc->ns_userid = getcpy(userid);
+}
+
+/*
+ * Get the snoop flag for this connection
+ */
+
+int
+netsec_get_snoop(netsec_context *nsc)
+{
+    return nsc->ns_snoop;
+}
+
+/*
+ * Set the snoop flag for this connection
+ */
+
+void
+netsec_set_snoop(netsec_context *nsc, int snoop)
+{
+    nsc->ns_snoop = snoop;
+}
+
+/*
+ * Set the snoop callback for this connection.
+ */
+
+void netsec_set_snoop_callback(netsec_context *nsc,
+                              netsec_snoop_callback callback, void *context)
+{
+    nsc->ns_snoop_cb = callback;
+    nsc->ns_snoop_context = context;
+}
+
+/*
+ * A base64-decoding snoop callback
+ */
+
+void
+netsec_b64_snoop_decoder(netsec_context *nsc, const char *string, size_t len,
+                        void *context)
+{
+    unsigned char *decoded;
+    size_t decodedlen;
+    int offset;
+    NMH_UNUSED(nsc);
+
+    offset = context ? *((int *) context) : 0;
+
+    if (offset > 0) {
+       /*
+        * Output non-base64 data first.
+        */
+       fprintf(stderr, "%.*s", offset, string);
+       string += offset;
+       len -= offset;
+    }
+
+    if (decodeBase64(string, &decoded, &decodedlen, 1, NULL) == OK) {
+       /*
+        * Some mechanisms preoduce large binary tokens, which aren't really
+        * readable.  So let's do a simple heuristic.  If the token is greater
+        * than 100 characters _and_ the first 100 bytes are more than 50%
+        * non-ASCII, then don't print the decoded buffer, just the
+        * base64 text.
+        */
+       if (decodedlen > 100 && !checkascii(decoded, 100)) {
+           fprintf(stderr, "%.*s\n", (int) len, string);
+       } else {
+           char *hexified;
+           hexify(decoded, decodedlen, &hexified);
+           fprintf(stderr, "b64<%s>\n", hexified);
+           free(hexified);
+       }
+       free(decoded);
+    } else {
+       fprintf(stderr, "%.*s\n", (int) len, string);
+    }
+}
+
+/*
+ * If the ASCII content is > 50%, return 1
+ */
+
+static int
+checkascii(const unsigned char *bytes, size_t len)
+{
+    size_t count = 0, half = len / 2;
+
+    while (len-- > 0) {
+       if (isascii(*bytes) && isprint(*bytes) && ++count > half)
+           return 1;
+       bytes++;
+       /* No chance by this point */
+       if (count + len < half)
+           return 0;
+    }
+
+    return 0;
+}
+
+/*
+ * Set the read timeout for this connection
+ */
+
+void
+netsec_set_timeout(netsec_context *nsc, int timeout)
+{
+    nsc->ns_timeout = timeout;
+}
+
+/*
+ * Read data from the network.  Basically, return anything in our buffer,
+ * otherwise fill from the network.
+ */
+
+ssize_t
+netsec_read(netsec_context *nsc, void *buffer, size_t size, char **errstr)
+{
+    int retlen;
+
+    /*
+     * If our buffer is empty, then we should fill it now
+     */
+
+    if (nsc->ns_inbuflen == 0) {
+       if (netsec_fillread(nsc, errstr) != OK)
+           return NOTOK;
+    }
+
+    /*
+     * netsec_fillread only returns if the buffer is full, so we can
+     * assume here that this has something in it.
+     */
+
+    retlen = size > nsc->ns_inbuflen ? nsc->ns_inbuflen : size;
+
+    memcpy(buffer, nsc->ns_inptr, retlen);
+
+    if (retlen == (int) nsc->ns_inbuflen) {
+       /*
+        * We've emptied our buffer, so reset everything.
+        */
+       nsc->ns_inptr = nsc->ns_inbuffer;
+       nsc->ns_inbuflen = 0;
+    } else {
+       nsc->ns_inptr += size;
+       nsc->ns_inbuflen -= size;
+    }
+
+    return OK;
+}
+
+/*
+ * Get a "line" (CR/LF) terminated from the network.
+ *
+ * Okay, we play some games here, so pay attention:
+ *
+ * - Unlike every other function, we return a pointer to the
+ *   existing buffer.  This pointer is valid until you call another
+ *   read function again.
+ * - We NUL-terminate the buffer right at the end, before the CR-LF terminator.
+ * - Technically we look for a LF; if we find a CR right before it, then
+ *   we back up one.
+ * - If your data may contain embedded NULs, this won't work.  You should
+ *   be using netsec_read() in that case.
+ */
+
+char *
+netsec_readline(netsec_context *nsc, size_t *len, char **errstr)
+{
+    unsigned char *ptr = nsc->ns_inptr;
+    size_t count = 0, offset;
+
+retry:
+    /*
+     * Search through our existing buffer for a LF
+     */
+
+    while (count < nsc->ns_inbuflen) {
+       count++;
+       if (*ptr++ == '\n') {
+           char *sptr = (char *) nsc->ns_inptr;
+           if (count > 1 && *(ptr - 2) == '\r')
+               ptr--;
+           *--ptr = '\0';
+           if (len)
+               *len = ptr - nsc->ns_inptr;
+           nsc->ns_inptr += count;
+           nsc->ns_inbuflen -= count;
+           if (nsc->ns_snoop) {
+#ifdef CYRUS_SASL
+               if (nsc->sasl_seclayer)
+                   fprintf(stderr, "(sasl-decrypted) ");
+#endif /* CYRUS_SASL */
+#ifdef TLS_SUPPORT
+               if (nsc->tls_active)
+                   fprintf(stderr, "(tls-decrypted) ");
+#endif /* TLS_SUPPORT */
+               fprintf(stderr, "<= ");
+               if (nsc->ns_snoop_cb)
+                   nsc->ns_snoop_cb(nsc, sptr, strlen(sptr),
+                                    nsc->ns_snoop_context);
+               else
+                   fprintf(stderr, "%s\n", sptr);
+           }
+           return sptr;
+       }
+    }
+
+    /*
+     * Hm, we didn't find a \n.  If we've already searched half of the input
+     * buffer, return an error.
+     */
+
+    if (count >= nsc->ns_inbufsize / 2) {
+       netsec_err(errstr, "Unable to find a line terminator after %d bytes",
+                  count);
+       return NULL;
+    }
+
+    /*
+     * Okay, get some more network data.  This may move inptr, so regenerate
+     * our ptr value;
+     */
+
+    offset = ptr - nsc->ns_inptr;
+
+    if (netsec_fillread(nsc, errstr) != OK)
+       return NULL;
+
+    ptr = nsc->ns_inptr + offset;
+
+    goto retry;
+
+    return NULL;       /* Should never reach this */
+}
+
+/*
+ * Fill our read buffer with some data from the network.
+ */
+
+static int
+netsec_fillread(netsec_context *nsc, char **errstr)
+{
+    unsigned char *end;
+    char *readbuf;
+    size_t readbufsize, remaining, startoffset;
+    int rc;
+
+    /*
+     * If inbuflen is zero, that means the buffer has been emptied
+     * completely.  In that case move inptr back to the start.
+     */
+
+    if (nsc->ns_inbuflen == 0) {
+       nsc->ns_inptr = nsc->ns_inbuffer;
+    }
+
+#ifdef CYRUS_SASL
+retry:
+#endif /* CYRUS_SASL */
+    /*
+     * If we are using TLS and there's anything pending, then skip the
+     * select call
+     */
+#ifdef TLS_SUPPORT
+    if (!nsc->tls_active || BIO_pending(nsc->ssl_io) == 0)
+#endif /* TLS_SUPPORT */
+    {
+       struct timeval tv;
+       fd_set rfds;
+
+       FD_ZERO(&rfds);
+       FD_SET(nsc->ns_readfd, &rfds);
+
+       tv.tv_sec = nsc->ns_timeout;
+       tv.tv_usec = 0;
+
+       rc = select(nsc->ns_readfd + 1, &rfds, NULL, NULL, &tv);
+
+       if (rc == -1) {
+           netsec_err(errstr, "select() while reading failed: %s",
+                      strerror(errno));
+           return NOTOK;
+       }
+
+       if (rc == 0) {
+           netsec_err(errstr, "read() timed out after %d seconds",
+                      nsc->ns_timeout);
+           return NOTOK;
+       }
+
+       /*
+        * At this point, we know that rc is 1, so there's not even any
+        * point to check to see if our descriptor is set in rfds.
+        */
+    }
+
+    startoffset = nsc->ns_inptr - nsc->ns_inbuffer;
+    remaining = nsc->ns_inbufsize - (startoffset + nsc->ns_inbuflen);
+    end = nsc->ns_inptr + nsc->ns_inbuflen;
+
+    /*
+     * If we are using TLS, then just read via the BIO.  But we still
+     * use our local buffer.
+     */
+#ifdef TLS_SUPPORT
+    if (nsc->tls_active) {
+       rc = BIO_read(nsc->ssl_io, end, remaining);
+       if (rc == 0) {
+           /*
+            * Either EOF, or possibly an error.  Either way, it was probably
+            * unexpected, so treat as error.
+            */
+           netsec_err(errstr, "TLS peer aborted connection");
+           return NOTOK;
+       } else if (rc < 0) {
+           /* Definitely an error */
+           netsec_err(errstr, "Read on TLS connection failed: %s",
+                      ERR_error_string(ERR_get_error(), NULL));
+           return NOTOK;
+       }
+
+       nsc->ns_inbuflen += rc;
+
+       return OK;
+    }
+#endif /* TLS_SUPPORT */
+
+    /*
+     * Okay, time to read some data.  Either we're just doing it straight
+     * or we're passing it through sasl_decode() first.
+     */
+
+#ifdef CYRUS_SASL
+    if (nsc->sasl_seclayer) {
+       readbuf = nsc->sasl_tmpbuf;
+       readbufsize = nsc->sasl_maxbufsize;
+    } else
+#endif /* CYRUS_SASL */
+    {
+       readbuf = (char *) end;
+       readbufsize = remaining;
+    }
+
+    /*
+     * At this point, we should have active data on the connection (see
+     * select() above) so this read SHOULDN'T block.  Hopefully.
+     */
+
+    rc = read(nsc->ns_readfd, readbuf, readbufsize);
+
+    if (rc == 0) {
+       netsec_err(errstr, "Received EOF on network read");
+       return NOTOK;
+    }
+
+    if (rc < 0) {
+       netsec_err(errstr, "Network read failed: %s", strerror(errno));
+       return NOTOK;
+    }
+
+    /*
+     * Okay, so we've had a successful read.  If we are doing SASL security
+     * layers, pass this through sasl_decode().  sasl_decode() can return
+     * 0 bytes decoded; if that happens, jump back to the beginning.  Otherwise
+     * we can just update our length pointer.
+     */
+
+#ifdef CYRUS_SASL
+    if (nsc->sasl_seclayer) {
+       const char *tmpout;
+       unsigned int tmpoutlen;
+
+       rc = sasl_decode(nsc->sasl_conn, nsc->sasl_tmpbuf, rc,
+                        &tmpout, &tmpoutlen);
+
+       if (rc != SASL_OK) {
+           netsec_err(errstr, "Unable to decode SASL network data: %s",
+                      sasl_errdetail(nsc->sasl_conn));
+           return NOTOK;
+       }
+
+       if (tmpoutlen == 0)
+           goto retry;
+
+       /*
+        * Just in case ...
+        */
+
+       if (tmpoutlen > remaining) {
+           netsec_err(errstr, "Internal error: SASL decode buffer overflow!");
+           return NOTOK;
+       }
+
+       memcpy(end, tmpout, tmpoutlen);
+
+       nsc->ns_inbuflen += tmpoutlen;
+    } else
+#endif /* CYRUS_SASL */
+       nsc->ns_inbuflen += rc;
+
+    /*
+     * If we're past the halfway point in our read buffers, shuffle everything
+     * back to the beginning.
+     */
+
+    if (startoffset > nsc->ns_inbufsize / 2) {
+       memmove(nsc->ns_inbuffer, nsc->ns_inptr, nsc->ns_inbuflen);
+       nsc->ns_inptr = nsc->ns_inbuffer;
+    }
+
+    return OK;
+}
+
+/*
+ * Write data to our network connection.  Really, fill up the buffer as
+ * much as we can, and flush it out if necessary.  netsec_flush() does
+ * the real work.
+ */
+
+int
+netsec_write(netsec_context *nsc, const void *buffer, size_t size,
+            char **errstr)
+{
+    const unsigned char *bufptr = buffer;
+    int rc, remaining;
+
+    /* Just in case */
+
+    if (size == 0)
+       return OK;
+
+    /*
+     * Run a loop copying in data to our local buffer; when we're done with
+     * any buffer overflows then just copy any remaining data in.
+     */
+
+    while ((int) size >= (remaining = nsc->ns_outbufsize - nsc->ns_outbuflen)) {
+       memcpy(nsc->ns_outptr, bufptr, remaining);
+
+       /*
+        * In theory I should increment outptr, but netsec_flush just resets
+        * it anyway.
+        */
+       nsc->ns_outbuflen = nsc->ns_outbufsize;
+
+       rc = netsec_flush(nsc, errstr);
+
+       if (rc != OK)
+           return NOTOK;
+
+       bufptr += remaining;
+       size -= remaining;
+    }
+
+    /*
+     * Copy any leftover data into the buffer.
+     */
+
+    if (size > 0) {
+       memcpy(nsc->ns_outptr, bufptr, size);
+       nsc->ns_outptr += size;
+       nsc->ns_outbuflen += size;
+    }
+
+    return OK;
+}
+
+/*
+ * Our network printf() routine, which really just calls netsec_vprintf().
+ */
+
+int
+netsec_printf(netsec_context *nsc, char **errstr, const char *format, ...)
+{
+    va_list ap;
+    int rc;
+
+    va_start(ap, format);
+    rc = netsec_vprintf(nsc, errstr, format, ap);
+    va_end(ap);
+
+    return rc;
+}
+
+/*
+ * Write bytes to the network using printf()-style formatting.
+ *
+ * Again, for the most part copy stuff into our buffer to be flushed
+ * out later.
+ */
+
+int
+netsec_vprintf(netsec_context *nsc, char **errstr, const char *format,
+              va_list ap)
+{
+    int rc;
+
+    /*
+     * Cheat a little.  If we can fit the data into our outgoing buffer,
+     * great!  If not, generate a flush and retry once.
+     */
+
+retry:
+    rc = vsnprintf((char *) nsc->ns_outptr,
+                  nsc->ns_outbufsize - nsc->ns_outbuflen, format, ap);
+
+    if (rc >= (int) (nsc->ns_outbufsize - nsc->ns_outbuflen)) {
+       /*
+        * This means we have an overflow.  Note that we don't actually
+        * make use of the terminating NUL, but according to the spec
+        * vsnprintf() won't write to the last byte in the string; that's
+        * why we have to use >= in the comparison above.
+        */
+       if (nsc->ns_outbuffer == nsc->ns_outptr) {
+           /*
+            * Whoops, if the buffer pointer was the same as the start of the
+            * buffer, that means we overflowed the internal buffer.
+            * At that point, just give up.
+            */
+           netsec_err(errstr, "Internal error: wanted to printf() a total of "
+                      "%d bytes, but our buffer size was only %d bytes",
+                      rc, nsc->ns_outbufsize);
+           return NOTOK;
+       } else {
+           /*
+            * Generate a flush (which may be inefficient, but hopefully
+            * it isn't) and then try again.
+            */
+           if (netsec_flush(nsc, errstr) != OK)
+               return NOTOK;
+           /*
+            * After this, outbuffer should == outptr, so we shouldn't
+            * hit this next time around.
+            */
+           goto retry;
+       }
+    }
+
+    if (nsc->ns_snoop) {
+       int outlen = rc;
+       if (outlen > 0 && nsc->ns_outptr[outlen - 1] == '\n') {
+           outlen--;
+           if (outlen > 0 && nsc->ns_outptr[outlen - 1] == '\r')
+               outlen--;
+       } else {
+           nsc->ns_snoop_noend = 1;
+       }
+       if (outlen > 0 || nsc->ns_snoop_noend == 0) {
+#ifdef CYRUS_SASL
+           if (nsc->sasl_seclayer)
+               fprintf(stderr, "(sasl-encrypted) ");
+#endif /* CYRUS_SASL */
+#ifdef TLS_SUPPORT
+           if (nsc->tls_active)
+               fprintf(stderr, "(tls-encrypted) ");
+#endif /* TLS_SUPPORT */
+           fprintf(stderr, "=> ");
+           if (nsc->ns_snoop_cb)
+               nsc->ns_snoop_cb(nsc, (char *) nsc->ns_outptr, outlen,
+                                nsc->ns_snoop_context);
+           else
+                fprintf(stderr, "%.*s\n", outlen, nsc->ns_outptr); 
+       } else {
+           nsc->ns_snoop_noend = 0;
+       }
+    }
+
+    nsc->ns_outptr += rc;
+    nsc->ns_outbuflen += rc;
+
+    return OK;
+}
+
+/*
+ * Flush out any buffered data in our output buffers.  This routine is
+ * actually where the real network writes take place.
+ */
+
+int
+netsec_flush(netsec_context *nsc, char **errstr)
+{
+    const char *netoutbuf = (const char *) nsc->ns_outbuffer;
+    unsigned int netoutlen = nsc->ns_outbuflen;
+    int rc;
+
+    /*
+     * Small optimization
+     */
+
+    if (netoutlen == 0)
+       return OK;
+
+    /*
+     * If SASL security layers are in effect, run the data through
+     * sasl_encode() first.
+     */
+#ifdef CYRUS_SASL
+    if (nsc->sasl_seclayer) {
+       rc = sasl_encode(nsc->sasl_conn, (const char *) nsc->ns_outbuffer,
+                        nsc->ns_outbuflen, &netoutbuf, &netoutlen);
+
+       if (rc != SASL_OK) {
+           netsec_err(errstr, "SASL data encoding failed: %s",
+                      sasl_errdetail(nsc->sasl_conn));
+           return NOTOK;
+       }
+
+    }
+#endif /* CYRUS_SASL */
+
+    /*
+     * If TLS is active, then use those functions to write out the
+     * data.
+     */
+#ifdef TLS_SUPPORT
+    if (nsc->tls_active) {
+       if (BIO_write(nsc->ssl_io, netoutbuf, netoutlen) <= 0) {
+           netsec_err(errstr, "Error writing to TLS connection: %s",
+                      ERR_error_string(ERR_get_error(), NULL));
+           return NOTOK;
+       }
+    } else
+#endif /* TLS_SUPPORT */
+    {
+       rc = write(nsc->ns_writefd, netoutbuf, netoutlen);
+
+       if (rc < 0) {
+           netsec_err(errstr, "write() failed: %s", strerror(errno));
+           return NOTOK;
+       }
+    }
+
+    nsc->ns_outptr = nsc->ns_outbuffer;
+    nsc->ns_outbuflen = 0;
+
+    return OK;
+}
+
+/*
+ * Set various SASL protocol parameters
+ */
+
+int
+netsec_set_sasl_params(netsec_context *nsc, const char *hostname,
+                      const char *service, const char *mechanism,
+                      netsec_sasl_callback callback, char **errstr)
+{
+#ifdef CYRUS_SASL
+    sasl_callback_t *sasl_cbs;
+    int retval;
+
+    if (! sasl_initialized) {
+       retval = sasl_client_init(NULL);
+       if (retval != SASL_OK) {
+           netsec_err(errstr, "SASL client initialization failed: %s",
+                      sasl_errstring(retval, NULL, NULL));
+           return NOTOK;
+       }
+       sasl_initialized++;
+    }
+
+    /*
+     * Allocate an array of SASL callbacks for this connection.
+     * Right now we just allocate an array of four callbacks.
+     */
+
+    sasl_cbs = mh_xmalloc(sizeof(*sasl_cbs) * 4);
+
+    sasl_cbs[0].id = SASL_CB_USER;
+    sasl_cbs[0].proc = (sasl_callback_ft) netsec_get_user;
+    sasl_cbs[0].context = nsc;
+
+    sasl_cbs[1].id = SASL_CB_AUTHNAME;
+    sasl_cbs[1].proc = (sasl_callback_ft) netsec_get_user;
+    sasl_cbs[1].context = nsc;
+
+    sasl_cbs[2].id = SASL_CB_PASS;
+    sasl_cbs[2].proc = (sasl_callback_ft) netsec_get_password;
+    sasl_cbs[2].context = nsc;
+
+    sasl_cbs[3].id = SASL_CB_LIST_END;
+    sasl_cbs[3].proc = NULL;
+    sasl_cbs[3].context = NULL;
+
+    nsc->sasl_cbs = sasl_cbs;
+
+    retval = sasl_client_new(service, hostname, NULL, NULL, nsc->sasl_cbs, 0,
+                            &nsc->sasl_conn);
+
+    if (retval) {
+       netsec_err(errstr, "SASL new client allocation failed: %s",
+                  sasl_errstring(retval, NULL, NULL));
+       return NOTOK;
+    }
+
+    nsc->sasl_hostname = getcpy(hostname);
+#else /* CYRUS_SASL */
+    NMH_UNUSED(hostname);
+    NMH_UNUSED(service);
+    NMH_UNUSED(errstr);
+#endif /* CYRUS_SASL */
+
+    /*
+     * According to the RFC, mechanisms can only be uppercase letter, numbers,
+     * and a hypen or underscore.  So make sure we uppercase any letters
+     * in case the user passed in lowercase.
+     */
+
+    if (mechanism) {
+       char *p;
+       nsc->sasl_mech = getcpy(mechanism);
+
+       for (p = nsc->sasl_mech; *p; p++)
+           if (isascii((unsigned char) *p))    /* Just in case */
+               *p = toupper((unsigned char) *p);
+    }
+
+    nsc->sasl_proto_cb = callback;
+
+    return OK;
+}
+
+#ifdef CYRUS_SASL
+/*
+ * Our userid callback; return the specified username to the SASL
+ * library when asked.
+ */
+
+int netsec_get_user(void *context, int id, const char **result,
+                   unsigned int *len)
+{
+    netsec_context *nsc = (netsec_context *) context;
+
+    if (! result || (id != SASL_CB_USER && id != SASL_CB_AUTHNAME))
+       return SASL_BADPARAM;
+
+    if (nsc->ns_userid == NULL) {
+       /*
+        * Pass the 1 third argument to nmh_get_credentials() so that
+        * a defauly user if the -user switch wasn't supplied, and so
+        * that a default password will be supplied.  That's used when
+        * those values really don't matter, and only with legacy/.netrc,
+        * i.e., with a credentials profile entry.
+        */
+
+       if (nsc->sasl_creds == NULL) {
+           nsc->sasl_creds = mh_xmalloc(sizeof(*nsc->sasl_creds));
+           nsc->sasl_creds->user = NULL;
+           nsc->sasl_creds->password = NULL;
+       }
+
+       if (nmh_get_credentials(nsc->sasl_hostname, nsc->ns_userid, 1,
+                               nsc->sasl_creds) != OK)
+           return SASL_BADPARAM;
+
+       if (nsc->ns_userid != nsc->sasl_creds->user) {
+           if (nsc->ns_userid)
+               free(nsc->ns_userid);
+           nsc->ns_userid = getcpy(nsc->sasl_creds->user);
+       }
+    }
+
+    *result = nsc->ns_userid;
+    if (len)
+       *len = strlen(nsc->ns_userid);
+
+    return SASL_OK;
+}
+
+/*
+ * Retrieve a password and return it to SASL
+ */
+
+static int
+netsec_get_password(sasl_conn_t *conn, void *context, int id,
+                   sasl_secret_t **psecret)
+{
+    netsec_context *nsc = (netsec_context *) context;
+    int len;
+
+    NMH_UNUSED(conn);
+
+    if (! psecret || id != SASL_CB_PASS)
+       return SASL_BADPARAM;
+
+    if (nsc->sasl_creds == NULL) {
+       nsc->sasl_creds = mh_xmalloc(sizeof(*nsc->sasl_creds));
+       nsc->sasl_creds->user = NULL;
+       nsc->sasl_creds->password = NULL;
+    }
+
+    if (nsc->sasl_creds->password == NULL) {
+       /*
+        * Pass the 0 third argument to nmh_get_credentials() so
+        * that the default password isn't used.  With legacy/.netrc
+        * credentials support, we'll only get here if the -user
+        * switch to send(1)/post(8) wasn't used.
+        */
+
+       if (nmh_get_credentials(nsc->sasl_hostname, nsc->ns_userid, 0,
+                               nsc->sasl_creds) != OK) {
+           return SASL_BADPARAM;
+       }
+    }
+
+    len = strlen(nsc->sasl_creds->password);
+
+    /*
+     * sasl_secret_t includes 1 bytes for "data" already, so that leaves
+     * us room for a terminating NUL
+     */
+
+    *psecret = (sasl_secret_t *) malloc(sizeof(sasl_secret_t) + len);
+
+    if (! *psecret)
+       return SASL_NOMEM;
+
+    (*psecret)->len = len;
+    strcpy((char *) (*psecret)->data, nsc->sasl_creds->password);
+
+    nsc->sasl_secret = *psecret;
+
+    return SASL_OK;
+}
+#endif /* CYRUS_SASL */
+
+/*
+ * Negotiate SASL on this connection
+ */
+
+int
+netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr)
+{
+#ifdef CYRUS_SASL
+    sasl_security_properties_t secprops;
+    const char *chosen_mech;
+    const unsigned char *saslbuf;
+    unsigned char *outbuf;
+    unsigned int saslbuflen, outbuflen;
+    sasl_ssf_t *ssf;
+    int *outbufmax;
+#endif
+#ifdef OAUTH_SUPPORT
+    unsigned char *xoauth_client_res;
+    size_t xoauth_client_res_len;
+#endif /* OAUTH_SUPPORT */
+#if defined CYRUS_SASL || defined OAUTH_SUPPORT
+    int rc;
+#endif /* CYRUS_SASL || OAUTH_SUPPORT */
+
+    /*
+     * If we've been passed a requested mechanism, check our mechanism
+     * list from the protocol.  If it's not supported, return an error.
+     */
+
+    if (nsc->sasl_mech) {
+       char **str, *mlist = getcpy(mechlist);
+       int i;
+
+       str = brkstring(mlist, " ", NULL);
+
+       for (i = 0; str[i] != NULL; i++) {
+           if (strcasecmp(nsc->sasl_mech, str[i]) == 0) {
+               break;
+           }
+       }
+
+       i = (str[i] == NULL);
+
+       free(mlist);
+
+       if (i) {
+           netsec_err(errstr, "Chosen mechanism %s not supported by server",
+                      nsc->sasl_mech);
+           return NOTOK;
+       }
+    }
+
+#ifdef OAUTH_SUPPORT
+    if (nsc->sasl_mech && strcasecmp(nsc->sasl_mech, "XOAUTH2") == 0) {
+       /*
+        * This should be relatively straightforward, but requires some
+        * help from the plugin.  Basically, if XOAUTH2 is a success,
+        * the callback has to return success, but no output data.  If
+        * there is output data, it will be assumed that it is the JSON
+        * error message.
+        */
+
+       if (! nsc->oauth_service) {
+           netsec_err(errstr, "Internal error: OAuth2 service name not given");
+           return NOTOK;
+       }
+
+       nsc->sasl_chosen_mech = getcpy(nsc->sasl_mech);
+
+       if (mh_oauth_do_xoauth(nsc->ns_userid, nsc->oauth_service,
+                              &xoauth_client_res, &xoauth_client_res_len,
+                              nsc->ns_snoop ? stderr : NULL) != OK) {
+           netsec_err(errstr, "Internal error: Unable to get OAuth2 "
+                      "bearer token");
+           return NOTOK;
+       }
+
+       rc = nsc->sasl_proto_cb(NETSEC_SASL_START, xoauth_client_res,
+                               xoauth_client_res_len, NULL, 0, errstr);
+       free(xoauth_client_res);
+
+       if (rc != OK)
+           return NOTOK;
+
+       /*
+        * Okay, we need to do a NETSEC_SASL_FINISH now.  If we return
+        * success, we indicate that with no output data.  But if we
+        * fail, then send a blank message and get the resulting
+        * error.
+        */
+
+       rc = nsc->sasl_proto_cb(NETSEC_SASL_FINISH, NULL, 0, NULL, 0, errstr);
+
+       if (rc != OK) {
+           /*
+            * We're going to assume the error here is a JSON response;
+            * we ignore it and send a blank message in response.  We should
+            * then get a failure messages with a useful error.  We should
+            * NOT get a success message at this point.
+            */
+           free(*errstr);
+           nsc->sasl_proto_cb(NETSEC_SASL_WRITE, NULL, 0, NULL, 0, NULL);
+           rc = nsc->sasl_proto_cb(NETSEC_SASL_FINISH, NULL, 0, NULL, 0,
+                                   errstr);
+           if (rc == 0) {
+               netsec_err(errstr, "Unexpected success after OAuth failure!");
+           }
+           return NOTOK;
+       }
+       return OK;
+    }
+#endif /* OAUTH_SUPPORT */
+
+#ifdef CYRUS_SASL
+    /*
+     * In netsec_set_sasl_params, we've already done all of our setup with
+     * sasl_client_init() and sasl_client_new().  So time to set security
+     * properties, call sasl_client_start(), and generate the protocol
+     * messages.
+     */
+
+    memset(&secprops, 0, sizeof(secprops));
+    secprops.maxbufsize = SASL_MAXRECVBUF;
+
+    /*
+     * If we're using TLS, do not negotiate a security layer
+     */
+
+    secprops.max_ssf = 
+#ifdef TLS_SUPPORT
+               nsc->tls_active ? 0 :
+#endif /* TLS_SUPPORT */
+               UINT_MAX;
+
+    rc = sasl_setprop(nsc->sasl_conn, SASL_SEC_PROPS, &secprops);
+
+    if (rc != SASL_OK) {
+       netsec_err(errstr, "SASL security property initialization failed: %s",
+                  sasl_errstring(rc, NULL, NULL));
+       return NOTOK;
+    }
+
+    /*
+     * Start the actual protocol negotiation, and go through the
+     * sasl_client_step() loop (after sasl_client_start, of course).
+     */
+
+    rc = sasl_client_start(nsc->sasl_conn,
+                          nsc->sasl_mech ? nsc->sasl_mech : mechlist, NULL,
+                          (const char **) &saslbuf, &saslbuflen,
+                          &chosen_mech);
+
+    if (rc != SASL_OK && rc != SASL_CONTINUE) {
+       netsec_err(errstr, "SASL client start failed: %s",
+                  sasl_errdetail(nsc->sasl_conn));
+       return NOTOK;
+    }
+
+    nsc->sasl_chosen_mech = getcpy(chosen_mech);
+
+    if (nsc->sasl_proto_cb(NETSEC_SASL_START, saslbuf, saslbuflen, NULL, 0,
+                          errstr) != OK)
+       return NOTOK;
+
+    /*
+     * We've written out our first message; enter in the step loop
+     */
+
+    while (rc == SASL_CONTINUE) {
+       /*
+        * Call our SASL callback, which will handle the details of
+        * reading data from the network.
+        */
+
+       if (nsc->sasl_proto_cb(NETSEC_SASL_READ, NULL, 0, &outbuf, &outbuflen,
+                              errstr) != OK) {
+           nsc->sasl_proto_cb(NETSEC_SASL_CANCEL, NULL, 0, NULL, 0, NULL);
+           return NOTOK;
+       }
+
+       rc = sasl_client_step(nsc->sasl_conn, (char *) outbuf, outbuflen, NULL,
+                             (const char **) &saslbuf, &saslbuflen);
+
+       if (outbuf)
+           free(outbuf);
+
+       if (rc != SASL_OK && rc != SASL_CONTINUE) {
+           netsec_err(errstr, "SASL client negotiation failed: %s",
+                      sasl_errdetail(nsc->sasl_conn));
+           nsc->sasl_proto_cb(NETSEC_SASL_CANCEL, NULL, 0, NULL, 0, NULL);
+           return NOTOK;
+       }
+
+       if (nsc->sasl_proto_cb(NETSEC_SASL_WRITE, saslbuf, saslbuflen,
+                              NULL, 0, errstr) != OK) {
+           nsc->sasl_proto_cb(NETSEC_SASL_CANCEL, NULL, 0, NULL, 0, NULL);
+           return NOTOK;
+       }
+    }
+
+    /*
+     * SASL exchanges should be complete, process the final response message
+     * from the server.
+     */
+
+    if (nsc->sasl_proto_cb(NETSEC_SASL_FINISH, NULL, 0, NULL, 0,
+                          errstr) != OK) {
+       /*
+        * At this point we can't really send an abort since the SASL dialog
+        * has completed, so just bubble back up the error message.
+        */
+
+       return NOTOK;
+    }
+
+    /*
+     * At this point, SASL should be complete.  Get a few properties
+     * from the authentication exchange.
+     */
+
+    rc = sasl_getprop(nsc->sasl_conn, SASL_SSF, (const void **) &ssf);
+
+    if (rc != SASL_OK) {
+       netsec_err(errstr, "Cannot retrieve SASL negotiated security "
+                 "strength factor: %s", sasl_errstring(rc, NULL, NULL));
+       return NOTOK;
+    }
+
+    nsc->sasl_ssf = *ssf;
+
+    if (nsc->sasl_ssf > 0) {
+       rc = sasl_getprop(nsc->sasl_conn, SASL_MAXOUTBUF,
+                         (const void **) &outbufmax);
+
+       if (rc != SASL_OK) {
+           netsec_err(errstr, "Cannot retrieve SASL negotiated output "
+                      "buffer size: %s", sasl_errstring(rc, NULL, NULL));
+           return NOTOK;
+       }
+
+       /*
+        * If our output buffer isn't the same size as the input buffer,
+        * reallocate it and set the new size (since we won't encode any
+        * data larger than that).
+        */
+
+       nsc->sasl_maxbufsize = *outbufmax;
+
+       if (nsc->ns_outbufsize != nsc->sasl_maxbufsize) {
+           nsc->ns_outbufsize = nsc->sasl_maxbufsize;
+           nsc->ns_outbuffer = mh_xrealloc(nsc->ns_outbuffer,
+                                           nsc->ns_outbufsize);
+           /*
+            * There shouldn't be any data in the buffer, but for
+            * consistency's sake discard it.
+            */
+           nsc->ns_outptr = nsc->ns_outbuffer;
+           nsc->ns_outbuflen = 0;
+       }
+
+       /*
+        * Allocate a buffer to do temporary reads into, before we
+        * call sasl_decode()
+        */
+
+       nsc->sasl_tmpbuf = mh_xmalloc(nsc->sasl_maxbufsize);
+
+       /*
+        * Okay, this is a bit weird.  Make sure that the input buffer
+        * is at least TWICE the size of the max buffer size.  That's
+        * because if we're consuming data but want to extend the current
+        * buffer, we want to be sure there's room for another full buffer's
+        * worth of data.
+        */
+
+       if (nsc->ns_inbufsize < nsc->sasl_maxbufsize * 2) {
+           size_t offset = nsc->ns_inptr - nsc->ns_inbuffer;
+           nsc->ns_inbufsize = nsc->sasl_maxbufsize * 2;
+           nsc->ns_inbuffer = mh_xrealloc(nsc->ns_inbuffer, nsc->ns_inbufsize);
+           nsc->ns_inptr = nsc->ns_inbuffer + offset;
+       }
+
+       nsc->sasl_seclayer = 1;
+    }
+
+    return OK;
+#else
+    /*
+     * If we're at this point, then either we have NEITHER OAuth2 or
+     * Cyrus-SASL compiled in, or have OAuth2 but didn't give the XOAUTH2
+     * mechanism on the command line.
+     */
+
+    if (! nsc->sasl_mech)
+       netsec_err(errstr, "SASL library support not available; please "
+                  "specify a SASL mechanism to use");
+    else
+       netsec_err(errstr, "No support for the %s SASL mechanism",
+                  nsc->sasl_mech);
+
+    return NOTOK;
+#endif /* CYRUS_SASL */
+}
+
+/*
+ * Retrieve our chosen SASL mechanism
+ */
+
+char *
+netsec_get_sasl_mechanism(netsec_context *nsc)
+{
+    return nsc->sasl_chosen_mech;
+}
+
+/*
+ * Set an OAuth2 service name, if we support it.
+ */
+
+int
+netsec_set_oauth_service(netsec_context *nsc, const char *service)
+{
+#ifdef OAUTH_SUPPORT
+    nsc->oauth_service = getcpy(service);
+    return OK;
+#else /* OAUTH_SUPPORT */
+    NMH_UNUSED(nsc);
+    NMH_UNUSED(service);
+    return NOTOK;
+#endif /* OAUTH_SUPPORT */
+}
+
+/*
+ * Initialize (and enable) TLS for this connection
+ */
+
+int
+netsec_set_tls(netsec_context *nsc, int tls, char **errstr)
+{
+    if (tls) {
+#ifdef TLS_SUPPORT
+       SSL *ssl;
+       BIO *rbio, *wbio, *ssl_bio;;
+
+       if (! tls_initialized) {
+           SSL_library_init();
+           SSL_load_error_strings();
+
+           /*
+            * Create the SSL context; this has the properties for all
+            * SSL connections (we are only doing one), though.  Make sure
+            * we only support secure (known as of now) TLS protocols.
+            */
+
+           sslctx = SSL_CTX_new(SSLv23_client_method());
+
+           if (! sslctx) {
+               netsec_err(errstr, "Unable to initialize OpenSSL context: %s",
+                          ERR_error_string(ERR_get_error(), NULL));
+               return NOTOK;
+           }
+
+           SSL_CTX_set_options(sslctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
+                               SSL_OP_NO_TLSv1);
+
+           tls_initialized++;
+       }
+
+       if (nsc->ns_readfd == -1 || nsc->ns_writefd == -1) {
+           netsec_err(errstr, "Invalid file descriptor in netsec context");
+           return NOTOK;
+       }
+
+       /*
+        * Create the SSL structure which holds the data for a single
+        * TLS connection.
+        */
+
+       ssl = SSL_new(sslctx);
+
+       if (! ssl) {
+           netsec_err(errstr, "Unable to create SSL connection: %s",
+                      ERR_error_string(ERR_get_error(), NULL));
+           return NOTOK;
+       }
+
+       /*
+        * Never bother us, since we are using blocking sockets.
+        */
+
+       SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
+
+       /*
+        * This is a bit weird, so pay attention.
+        *
+        * We create a socket BIO, and bind it to our SSL connection.
+        * That means reads and writes to the SSL connection will use our
+        * supplied socket.
+        *
+        * Then we create an SSL BIO, and assign our current SSL connection
+        * to it.  This is done so our code stays simple if we want to use
+        * any buffering BIOs (right now we do our own buffering).
+        * So the chain looks like:
+        *
+        * SSL BIO -> socket BIO.
+        */
+
+       rbio = BIO_new_socket(nsc->ns_readfd, BIO_NOCLOSE);
+
+       if (! rbio) {
+           netsec_err(errstr, "Unable to create a read socket BIO: %s",
+                      ERR_error_string(ERR_get_error(), NULL));
+           SSL_free(ssl);
+           return NOTOK;
+       }
+
+       wbio = BIO_new_socket(nsc->ns_writefd, BIO_NOCLOSE);
+
+       if (! wbio) {
+           netsec_err(errstr, "Unable to create a write socket BIO: %s",
+                      ERR_error_string(ERR_get_error(), NULL));
+           SSL_free(ssl);
+           BIO_free(rbio);
+           return NOTOK;
+       }
+
+       SSL_set_bio(ssl, rbio, wbio);
+       SSL_set_connect_state(ssl);
+
+       ssl_bio = BIO_new(BIO_f_ssl());
+
+       if (! ssl_bio) {
+           netsec_err(errstr, "Unable to create a SSL BIO: %s",
+                      ERR_error_string(ERR_get_error(), NULL));
+           SSL_free(ssl);
+           return NOTOK;
+       }
+
+       BIO_set_ssl(ssl_bio, ssl, BIO_CLOSE);
+       nsc->ssl_io = ssl_bio;
+
+       return OK;
+    } else {
+       BIO_free_all(nsc->ssl_io);
+       nsc->ssl_io = NULL;
+
+       return OK;
+    }
+#else /* TLS_SUPPORT */
+       netsec_err(errstr, "TLS is not supported");
+
+       return NOTOK;
+    }
+#endif /* TLS_SUPPORT */
+}
+
+/*
+ * Start TLS negotiation on this connection
+ */
+
+int
+netsec_negotiate_tls(netsec_context *nsc, char **errstr)
+{
+#ifdef TLS_SUPPORT
+    if (! nsc->ssl_io) {
+       netsec_err(errstr, "TLS has not been configured for this connection");
+       return NOTOK;
+    }
+
+    if (BIO_do_handshake(nsc->ssl_io) < 1) {
+       netsec_err(errstr, "TLS negotiation failed: %s",
+                  ERR_error_string(ERR_get_error(), NULL));
+       return NOTOK;
+    }
+
+    if (nsc->ns_snoop) {
+       SSL *ssl;
+
+       if (BIO_get_ssl(nsc->ssl_io, &ssl) < 1) {
+           fprintf(stderr, "WARNING: cannot determine SSL ciphers\n");
+       } else {
+           const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl);
+           fprintf(stderr, "TLS negotiation successful: %s(%d) %s\n",
+                   SSL_CIPHER_get_name(cipher),
+                   SSL_CIPHER_get_bits(cipher, NULL),
+                   SSL_CIPHER_get_version(cipher));
+       }
+    }
+
+    nsc->tls_active = 1;
+
+    return OK;
+#else /* TLS_SUPPORT */
+    netsec_err(errstr, "TLS not supported");
+
+    return NOTOK;
+#endif /* TLS_SUPPORT */
+}
+
+/*
+ * Generate an (allocated) error string
+ */
+
+void
+netsec_err(char **errstr, const char *fmt, ...)
+{
+    va_list ap;
+    size_t errbufsize;
+    char *errbuf = NULL;
+    int rc = 127;
+
+    if (! errstr)
+       return;
+
+    do {
+       errbufsize = rc + 1;
+       errbuf = mh_xrealloc(errbuf, errbufsize);
+       va_start(ap, fmt);
+       rc = vsnprintf(errbuf, errbufsize, fmt, ap);
+       va_end(ap);
+    } while (rc >= (int) errbufsize);
+
+    *errstr = errbuf;
+}
index 3433fba..f4564d0 100755 (executable)
@@ -127,17 +127,16 @@ static boolean get_json_strings(const char *, size_t, FILE *, ...);
 static boolean make_query_url(char *, size_t, CURL *, const char *, ...);
 static boolean post(struct curl_ctx *, const char *, const char *);
 
-char *
-mh_oauth_do_xoauth(const char *user, const char *svc, FILE *log)
+int
+mh_oauth_do_xoauth(const char *user, const char *svc, unsigned char **oauth_res,
+                  size_t *oauth_res_len, FILE *log)
 {
     mh_oauth_ctx *ctx;
     mh_oauth_cred *cred;
     char *fn;
     int failed_to_lock = 0;
     FILE *fp;
-    size_t client_res_len;
     char *client_res;
-    char *client_res_b64;
 
     if (!mh_oauth_new (&ctx, svc)) adios(NULL, mh_oauth_get_err_string(ctx));
 
@@ -183,18 +182,14 @@ mh_oauth_do_xoauth(const char *user, const char *svc, FILE *log)
     free(fn);
 
     /* XXX writeBase64raw modifies the source buffer!  make a copy */
-    client_res = getcpy(mh_oauth_sasl_client_response(&client_res_len, user,
+    client_res = getcpy(mh_oauth_sasl_client_response(oauth_res_len, user,
                                                       cred));
     mh_oauth_cred_free(cred);
     mh_oauth_free(ctx);
-    client_res_b64 = mh_xmalloc(((((client_res_len) + 2) / 3 ) * 4) + 1);
-    if (writeBase64raw((unsigned char *)client_res, client_res_len,
-                       (unsigned char *)client_res_b64) != OK) {
-        adios(NULL, "base64 encoding of XOAUTH2 client response failed");
-    }
-    free(client_res);
 
-    return client_res_b64;
+    *oauth_res = (unsigned char *) client_res;
+
+    return OK;
 }
 
 static boolean
index b2c7a7d..4843a24 100644 (file)
@@ -44,7 +44,7 @@ HOME=$MH_TEST_DIR
 export HOME
 
 unset MAILDROP MHBUILD MHCONTEXT MHMTSUSERCONF MHN MHSHOW MHSTORE
-unset MHLDEBUG MHPDEBUG MHWDEBUG PAGER
+unset MHLDEBUG MHPDEBUG MHWDEBUG PAGER XOAUTH
 #### Set LC_ALL in individual tests as needed.  Unset these so
 #### that we don't depend on user's settings in other tests.
 unset LANG LC_ALL LC_CTYPE
index ab0bad5..d2ac2a2 100644 (file)
@@ -131,7 +131,7 @@ expect_creds() {
 }
 
 test_inc() {
-    run_test "inc -host 127.0.0.1 -port ${pop_port} -saslmech xoauth2 -authservice test -user nobody@example.com -width 80" "$@"
+    run_test "inc -host 127.0.0.1 -port ${pop_port} -sasl -saslmech xoauth2 -authservice test -user nobody@example.com -width 80" "$@"
 }
 
 test_inc_success() {
@@ -142,7 +142,7 @@ test_inc_success() {
 }
 
 test_send_no_servers() {
-    run_test "send -draft -server 127.0.0.1 -port ${smtp_port} -saslmech xoauth2 -authservice test -user nobody@example.com" "$@"
+    run_test "send -draft -server 127.0.0.1 -port ${smtp_port} -sasl -saslmech xoauth2 -authservice test -user nobody@example.com" "$@"
 }
 
 test_send_only_fakesmtp() {
index c745692..96a2af0 100755 (executable)
@@ -77,6 +77,7 @@ Content-Type: application/json
 EOF
 
 start_fakehttp
+start_pop_xoauth
 
 test_inc 'inc: error refreshing OAuth2 token
 inc: bad OAuth request; re-run with -snoop and send REDACTED output to nmh-workers'
@@ -93,7 +94,7 @@ EOF
 
 start_pop_xoauth
 
-test_inc 'inc: -ERR [AUTH] Invalid credentials.'
+test_inc 'inc: Authentication failed: -ERR [AUTH] Invalid credentials.'
 
 # TEST
 start_test "pop server doesn't support oauth"
index 98efb2b..dc10080 100755 (executable)
@@ -35,7 +35,7 @@ expire-nobody@example.com: 2000000000
 EOF
 
 start_fakesmtp
-run_test "send -draft -server 127.0.0.1 -port ${smtp_port} -saslmech xoauth2 -authservice test -user nobody@example.com"
+run_test "send -draft -server 127.0.0.1 -port ${smtp_port} -sasl -saslmech xoauth2 -authservice test -user nobody@example.com"
 
 # TEST
 start_test 'expired access token, refresh works, smtp server accepts message'
@@ -163,7 +163,7 @@ setup_draft
 # TEST
 start_test 'no service definition'
 
-run_test "send -draft -server 127.0.0.1 -port ${smtp_port} -saslmech xoauth2 -authservice bogus -user nobody@example.com" 'send: Unable to retrieve oauth profile entries: scope is missing'
+run_test "send -draft -server 127.0.0.1 -port ${smtp_port} -sasl -saslmech xoauth2 -authservice bogus -user nobody@example.com" 'send: Unable to retrieve oauth profile entries: scope is missing'
 
 # TEST
 start_test 'no creds file -- should tell user to mhlogin'
@@ -269,7 +269,7 @@ EOF
 
 start_fakesmtp
 run_prog send -draft -server 127.0.0.1 -port ${smtp_port} \
-  -saslmech xoauth2 -authservice test -user nobody@example.com > "${testname}.send-output" 2>&1 || true
+  -sasl -saslmech xoauth2 -authservice test -user nobody@example.com > "${testname}.send-output" 2>&1 || true
 # Clear out an error message we get from libcurl on some systems (seen at least
 # 3 different versions of this error message, on FreeBSD 10.1, Ubuntu 12.04, and
 # Ubuntu 14.04).
@@ -358,7 +358,7 @@ access-nobody@example.com: test-access
 expire-nobody@example.com: 2000000000
 EOF
 
-test_send_only_fakesmtp 'post: problem initializing server; [BHST] Not no way, not no how!
+test_send_only_fakesmtp 'post: problem initializing server; [BHST] Authentication failed: Not no way, not no how!
 send: message not delivered to anyone'
 
 # TEST
@@ -385,7 +385,7 @@ start_test "smtp server doesn't support oauth"
 
 unset XOAUTH
 
-test_send_only_fakesmtp 'post: problem initializing server; [BHST] SMTP server does not support SASL XOAUTH2
+test_send_only_fakesmtp 'post: problem initializing server; [BHST] SMTP server does not support SASL
 send: message not delivered to anyone'
 
 clean_fakesmtp
index 4ed020b..3c68a0f 100755 (executable)
@@ -30,7 +30,7 @@ EOF
 sendfrom="$MH_INST_DIR${datarootdir}/doc/nmh/contrib/sendfrom"
 cat >> "$MH" <<EOF
 sendfrom-nobody@example.com: -server 127.0.0.1 -port ${smtp_port}
-  -client localhost -saslmech xoauth2 -authservice test -user user@example.com
+  -client localhost -sasl -saslmech xoauth2 -authservice test -user user@example.com
 EOF
 
 cat > "${testname}.expected" <<EOF
index ab89a3f..10aa913 100755 (executable)
@@ -42,7 +42,7 @@ http://127.0.0.1:${http_port}/oauth/auth?response_type=code&client_id=test-id&re
 Enter the authorization code: "
 
 start_pop_xoauth
-run_test "msgchk -host 127.0.0.1 -port ${pop_port} -saslmech xoauth2 -authservice test -user nobody@example.com" 'nobody@example.com has 1 message (178 bytes) on 127.0.0.1'
+run_test "msgchk -host 127.0.0.1 -port ${pop_port} -sasl -saslmech xoauth2 -authservice test -user nobody@example.com" 'nobody@example.com has 1 message (178 bytes) on 127.0.0.1'
 
 start_pop_xoauth
 test_inc_success
@@ -74,7 +74,7 @@ start_pop_xoauth
 test_inc_success
 
 start_pop_xoauth
-run_test "msgchk -host 127.0.0.1 -port ${pop_port} -saslmech oauth2 -authservice test -user nobody@example.com" 'nobody@example.com has 1 message (178 bytes) on 127.0.0.1'
+run_test "msgchk -host 127.0.0.1 -port ${pop_port} -sasl -saslmech xoauth2 -authservice test -user nobody@example.com" 'nobody@example.com has 1 message (178 bytes) on 127.0.0.1'
 
 setup_draft
 test_send_only_fakesmtp
@@ -100,7 +100,7 @@ EOF
 
 start_fakehttp
 start_pop_xoauth
-run_test "msgchk -host 127.0.0.1 -port ${pop_port} -saslmech xoauth2 -authservice test -user nobody@example.com" 'nobody@example.com has 1 message (178 bytes) on 127.0.0.1'
+run_test "msgchk -host 127.0.0.1 -port ${pop_port} -sasl -saslmech xoauth2 -authservice test -user nobody@example.com" 'nobody@example.com has 1 message (178 bytes) on 127.0.0.1'
 
 start_pop_xoauth
 test_inc_success
@@ -131,7 +131,7 @@ setup_draft
 test_send
 
 start_pop_xoauth
-run_test "msgchk -host 127.0.0.1 -port ${pop_port} -saslmech xoauth2 -authservice test -user nobody@example.com" 'nobody@example.com has 1 message (178 bytes) on 127.0.0.1'
+run_test "msgchk -host 127.0.0.1 -port ${pop_port} -sasl -saslmech xoauth2 -authservice test -user nobody@example.com" 'nobody@example.com has 1 message (178 bytes) on 127.0.0.1'
 
 start_pop_xoauth
 test_inc_success
index 2d704f1..0f2dda8 100755 (executable)
@@ -51,6 +51,8 @@ QUIT
 EOF
 
 cat > "${testname}.post-expected" <<EOF
+Trying to connect to "127.0.0.1" ...
+Connecting to 127.0.0.1:${localport}...
 <= 220 Not really an ESMTP server
 => EHLO nosuchhost.example.com
 <= 250-ready
@@ -76,7 +78,7 @@ cat > "${testname}.post-expected" <<EOF
 EOF
 
 test_post "${testname}.actual" "${testname}.expected" \
-    > ${testname}.post 2>/dev/null
+    2> ${testname}.post
 
 check "${testname}.post-expected" "${testname}.post"
 
index bf38254..8a71eb2 100644 (file)
--- a/uip/inc.c
+++ b/uip/inc.c
 #include <h/tws.h>
 #include <h/mts.h>
 
-#ifndef CYRUS_SASL
-# define SASLminc(a) (a)
+#ifndef TLS_SUPPORT
+# define TLSminc(a) (a)
 #else
-# define SASLminc(a)  0
+# define TLSminc(a)  0
 #endif
 
 #define INC_SWITCHES \
     X("version", 0, VERSIONSW) \
     X("help", 0, HELPSW) \
     X("snoop", 0, SNOOPSW) \
-    X("sasl", SASLminc(5), SASLSW) \
-    X("nosasl", SASLminc(3), NOSASLSW) \
-    X("saslmech", SASLminc(5), SASLMECHSW) \
-    X("authservice", SASLminc(0), AUTHSERVICESW) \
+    X("sasl", 0, SASLSW) \
+    X("nosasl", 0, NOSASLSW) \
+    X("saslmech", 0, SASLMECHSW) \
+    X("initialtls", TLSminc(-10), INITTLSSW) \
+    X("notls", TLSminc(-5), NOTLSSW) \
+    X("authservice", 0, AUTHSERVICESW) \
     X("proxy command", 0, PROXYSW) \
 
 #define X(sw, minchars, id) id,
@@ -186,7 +188,7 @@ main (int argc, char **argv)
     int chgflag = 1, trnflag = 1;
     int noisy = 1, width = -1;
     int hghnum = 0, msgnum = 0;
-    int sasl = 0;
+    int sasl = 0, tls = 0;
     int incerr = 0; /* <0 if inc hits an error which means it should not truncate mailspool */
     char *cp, *maildir = NULL, *folder = NULL;
     char *format = NULL, *form = NULL;
@@ -234,13 +236,13 @@ main (int argc, char **argv)
     while ((cp = *argp++)) {
        if (*cp == '-') {
            switch (smatch (++cp, switches)) {
-           case AMBIGSW: 
+           case AMBIGSW:
                ambigsw (cp, switches);
                done (1);
-           case UNKWNSW: 
+           case UNKWNSW:
                adios (NULL, "-%s unknown", cp);
 
-           case HELPSW: 
+           case HELPSW:
                snprintf (buf, sizeof(buf), "%s [+folder] [switches]", invo_name);
                print_help (buf, switches, 1);
                done (0);
@@ -248,19 +250,19 @@ main (int argc, char **argv)
                print_version(invo_name);
                done (0);
 
-           case AUDSW: 
+           case AUDSW:
                if (!(cp = *argp++) || *cp == '-')
                    adios (NULL, "missing argument to %s", argp[-2]);
                audfile = getcpy (m_maildir (cp));
                continue;
-           case NAUDSW: 
+           case NAUDSW:
                audfile = NULL;
                continue;
 
-           case CHGSW: 
+           case CHGSW:
                chgflag++;
                continue;
-           case NCHGSW: 
+           case NCHGSW:
                chgflag = 0;
                continue;
 
@@ -271,14 +273,14 @@ main (int argc, char **argv)
             * 1 by default (truncating is default)
             * 0 if -notruncate is given
             */
-           case TRNCSW: 
+           case TRNCSW:
                trnflag = 2;
                continue;
-           case NTRNCSW: 
+           case NTRNCSW:
                trnflag = 0;
                continue;
 
-           case FILESW: 
+           case FILESW:
                if (!(cp = *argp++) || *cp == '-')
                    adios (NULL, "missing argument to %s", argp[-2]);
                from = path (cp, TFILE);
@@ -291,25 +293,25 @@ main (int argc, char **argv)
                    trnflag = 0;
                continue;
 
-           case SILSW: 
+           case SILSW:
                noisy = 0;
                continue;
-           case NSILSW: 
+           case NSILSW:
                noisy++;
                continue;
 
-           case FORMSW: 
+           case FORMSW:
                if (!(form = *argp++) || *form == '-')
                    adios (NULL, "missing argument to %s", argp[-2]);
                format = NULL;
                continue;
-           case FMTSW: 
+           case FMTSW:
                if (!(format = *argp++) || *format == '-')
                    adios (NULL, "missing argument to %s", argp[-2]);
                form = NULL;
                continue;
 
-           case WIDTHSW: 
+           case WIDTHSW:
                if (!(cp = *argp++) || *cp == '-')
                    adios (NULL, "missing argument to %s", argp[-2]);
                width = atoi (cp);
@@ -354,6 +356,14 @@ main (int argc, char **argv)
                    adios (NULL, "missing argument to %s", argp[-2]);
                continue;
 
+           case INITTLSSW:
+               tls++;
+               continue;
+
+           case NOTLSSW:
+               tls = 0;
+               continue;
+
            case AUTHSERVICESW:
 #ifdef OAUTH_SUPPORT
                 if (!(auth_svc = *argp++) || *auth_svc == '-')
@@ -421,7 +431,7 @@ main (int argc, char **argv)
         * initialize POP connection
         */
        if (pop_init (host, port, creds.user, creds.password, proxy, snoop,
-                     sasl, saslmech, auth_svc) == NOTOK)
+                     sasl, saslmech, tls, auth_svc) == NOTOK)
            adios (NULL, "%s", response);
 
        /* Check if there are any messages */
@@ -657,7 +667,7 @@ go_to_it:
            switch (incerr = scan (pf, msgnum, 0, nfs, width,
                              packfile ? 0 : msgnum == mp->hghmsg + 1 && chgflag,
                              1, NULL, stop - start, noisy, &scanl)) {
-           case SCNEOF: 
+           case SCNEOF:
                printf ("%*d  empty\n", DMAXFOLDER, msgnum);
                break;
 
@@ -668,12 +678,12 @@ go_to_it:
                /* fall thru */
 
            case SCNERR:
-           case SCNNUM: 
+           case SCNNUM:
                break;
 
-           case SCNMSG: 
+           case SCNMSG:
            case SCNENC:
-           default: 
+           default:
                if (aud)
                    fputs (charstring_buffer (scanl), aud);
                if (noisy)
@@ -733,7 +743,7 @@ go_to_it:
                              msgnum == hghnum && chgflag, 1, NULL, 0L, noisy,
                              &scanl)) {
            case SCNFAT:
-           case SCNEOF: 
+           case SCNEOF:
                break;
 
            case SCNERR:
@@ -742,11 +752,11 @@ go_to_it:
                advise (NULL, "aborted!");      /* doesn't clean up locks! */
                break;
 
-           case SCNNUM: 
+           case SCNNUM:
                advise (NULL, "BUG in %s, number out of range", invo_name);
                break;
 
-           default: 
+           default:
                advise (NULL, "BUG in %s, scan() botch (%d)", invo_name, incerr);
                break;
 
@@ -807,7 +817,7 @@ go_to_it:
                }
                fclose (sf);
                sf = NULL;
-           } 
+           }
            if (pf == NULL && (pf = fopen (cp, "r")) == NULL)
                adios (cp, "not available");
            chmod (cp, m_gmprot ());
@@ -816,7 +826,7 @@ go_to_it:
            switch (incerr = scan (pf, msgnum, 0, nfs, width,
                              msgnum == mp->hghmsg + 1 && chgflag,
                              1, NULL, stop - start, noisy, &scanl)) {
-           case SCNEOF: 
+           case SCNEOF:
                printf ("%*d  empty\n", DMAXFOLDER, msgnum);
                break;
 
@@ -827,12 +837,12 @@ go_to_it:
                /* fall thru */
 
            case SCNERR:
-           case SCNNUM: 
+           case SCNNUM:
                break;
 
-           case SCNMSG: 
+           case SCNMSG:
            case SCNENC:
-           default: 
+           default:
                /*
                 *  Run the external program hook on the message.
                 */
index d78ca7e..7ec030b 100644 (file)
@@ -1751,7 +1751,7 @@ openBase64 (CT ct, char **file)
     /* sbeck -- handle suffixes */
     CI ci;
     CE ce = &ct->c_cefile;
-    const char *decoded;
+    unsigned char *decoded;
     size_t decoded_len;
     unsigned char digest[16];
 
@@ -1840,7 +1840,7 @@ openBase64 (CT ct, char **file)
     if (decodeBase64 (buffer, &decoded, &decoded_len, ct->c_type == CT_TEXT,
                       ct->c_digested ? digest : NULL) == OK) {
         size_t i;
-        const char *decoded_p = decoded;
+        unsigned char *decoded_p = decoded;
         for (i = 0; i < decoded_len; ++i) {
             putc (*decoded_p++, ce->ce_fp);
         }
@@ -2907,13 +2907,13 @@ openURL (CT ct, char **file)
 static int
 readDigest (CT ct, char *cp)
 {
-    const char *digest;
+    unsigned char *digest;
 
     size_t len;
     if (decodeBase64 (cp, &digest, &len, 0, NULL) == OK) {
         const size_t maxlen = sizeof ct->c_digest / sizeof ct->c_digest[0];
 
-        if (strlen (digest) <= maxlen) {
+        if (strlen ((char *) digest) <= maxlen) {
             memcpy (ct->c_digest, digest, maxlen);
 
             if (debugsw) {
@@ -2930,7 +2930,7 @@ readDigest (CT ct, char *cp)
         } else {
             if (debugsw) {
                 fprintf (stderr, "invalid MD5 digest (got %d octets)\n",
-                         (int) strlen (digest));
+                         (int) strlen ((char *) digest));
             }
 
             return NOTOK;
index b6a5d80..5861dae 100644 (file)
@@ -350,7 +350,7 @@ remotemail (char *host, char *port, char *user, char *proxy, int notifysw,
 
     /* open the POP connection */
     if (pop_init (host, port, creds.user, creds.password, proxy, snoop, sasl,
-                 saslmech, auth_svc) == NOTOK
+                 saslmech, 0, auth_svc) == NOTOK
            || pop_stat (&nmsgs, &nbytes) == NOTOK     /* check for messages  */
            || pop_quit () == NOTOK) {                 /* quit POP connection */
        advise (NULL, "%s", response);
index 5b99c8c..cf6b804 100644 (file)
@@ -9,18 +9,7 @@
 #include <h/mh.h>
 #include <h/utils.h>
 #include <h/oauth.h>
-
-#ifdef CYRUS_SASL
-# include <sasl/sasl.h>
-# include <sasl/saslutil.h>
-# if SASL_VERSION_FULL < 0x020125
-    /* Cyrus SASL 2.1.25 introduced the sasl_callback_ft prototype,
-       which has an explicit void parameter list, according to best
-       practice.  So we need to cast to avoid compile warnings.
-       Provide this prototype for earlier versions. */
-    typedef int (*sasl_callback_ft)();
-# endif /* SASL_VERSION_FULL < 0x020125 */
-#endif /* CYRUS_SASL */
+#include <h/netsec.h>
 
 #include <h/popsbr.h>
 #include <h/signals.h>
@@ -32,36 +21,7 @@ static int poprint = 0;
 static int pophack = 0;
 
 char response[BUFSIZ];
-
-static FILE *input;
-static FILE *output;
-
-#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 *password;
-    char *host;
-};
-
-static sasl_callback_t callbacks[] = {
-    { SASL_CB_USER, (sasl_callback_ft) sasl_get_user, NULL },
-#define POP_SASL_CB_N_USER 0
-    { SASL_CB_PASS, (sasl_callback_ft) sasl_get_pass, NULL },
-#define POP_SASL_CB_N_PASS 1
-    { SASL_CB_LOG, NULL, NULL },
-    { SASL_CB_LIST_END, NULL, NULL },
-
-#define SASL_BUFFER_SIZE 262144
-};
-#else /* CYRUS_SASL */
-# define sasl_fgetc fgetc
-#endif /* CYRUS_SASL */
+static netsec_context *nsc = NULL;
 
 /*
  * static prototypes
@@ -70,19 +30,15 @@ static sasl_callback_t callbacks[] = {
 static int command(const char *, ...);
 static int multiline(void);
 
-#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 sasl_getline (char *, int, FILE *);
-static int putline (char *, FILE *);
+static int pop_getline (char *, int, netsec_context *);
+static int pop_sasl_callback(enum sasl_message_type, unsigned const char *,
+                            unsigned int, unsigned char **, unsigned int *,
+                            char **);
 
-
-int
-check_mech(char *server_mechs, size_t server_mechs_size, char *mech)
+static int
+check_mech(char *server_mechs, size_t server_mechs_size)
 {
   int status, sasl_capability = 0;
 
@@ -125,337 +81,9 @@ check_mech(char *server_mechs, size_t server_mechs_size, char *mech)
        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;
-    }
-
     return OK;
 }
 
-#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.
- */
-
-#define CHECKB64SIZE(insize, outbuf, outsize) \
-    { size_t wantout = (((insize + 2) / 3) * 4) + 32; \
-      if (wantout > outsize) { \
-          outbuf = mh_xrealloc(outbuf, outsize = wantout); \
-      } \
-    }
-
-int
-pop_auth_sasl(char *user, char *host, char *mech)
-{
-    int result, status;
-    unsigned int buflen, outlen;
-    char server_mechs[256], *buf, *outbuf = NULL;
-    size_t outbufsize = 0;
-    const char *chosen_mech;
-    sasl_security_properties_t secprops;
-    struct pass_context p_context;
-    sasl_ssf_t *ssf;
-    int *moutbuf;
-
-    if ((status = check_mech(server_mechs, sizeof(server_mechs), mech)) != OK) {
-       return status;
-    }
-
-    /*
-     * 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 = SASL_BUFFER_SIZE;
-    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) {
-       CHECKB64SIZE(buflen, outbuf, outbufsize);
-       status = sasl_encode64(buf, buflen, outbuf, outbufsize, NULL);
-       if (status != SASL_OK) {
-           snprintf(response, sizeof(response), "SASL base64 encode "
-                    "failed: %s", sasl_errstring(status, NULL, NULL));
-           if (outbuf)
-               free(outbuf);
-           return NOTOK;
-       }
-
-       status = command("AUTH %s %s", chosen_mech, outbuf);
-    } else
-       status = command("AUTH %s", chosen_mech);
-
-    while (result == SASL_CONTINUE) {
-       size_t inlen;
-
-       if (status == NOTOK) {
-           if (outbuf)
-               free(outbuf);
-           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");
-           if (outbuf)
-               free(outbuf);
-           return NOTOK;
-       }
-
-       /*
-        * For decode, it will always be shorter, so just make sure
-        * that outbuf is as at least as big as the encoded response.
-        */
-
-       inlen = strlen(response + 2);
-
-       if (inlen > outbufsize) {
-           outbuf = mh_xrealloc(outbuf, outbufsize = inlen);
-       }
-
-       result = sasl_decode64(response + 2, strlen(response + 2),
-                              outbuf, outbufsize, &outlen);
-       
-       if (result != SASL_OK) {
-           command("*");
-           snprintf(response, sizeof(response), "SASL base64 decode "
-                    "failed: %s", sasl_errstring(result, NULL, NULL));
-           if (outbuf)
-               free(outbuf);
-           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));
-           if (outbuf)
-               free(outbuf);
-           return NOTOK;
-       }
-
-       CHECKB64SIZE(buflen, outbuf, outbufsize);
-
-       status = sasl_encode64(buf, buflen, outbuf, outbufsize, NULL);
-
-       if (status != SASL_OK) {
-           command("*");
-           snprintf(response, sizeof(response), "SASL base64 encode "
-                    "failed: %s", sasl_errstring(status, NULL, NULL));
-           if (outbuf)
-               free(outbuf);
-           return NOTOK;
-       }
-
-       status = command(outbuf);
-    }
-
-    if (outbuf)
-       free(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.  Shouldn't matter much because
-     * this is only outgoing data (which should be small)
-     */
-
-    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;
-    struct nmh_creds creds = { 0, 0, 0 };
-    int len;
-
-    NMH_UNUSED (conn);
-
-    if (! psecret || id != SASL_CB_PASS)
-       return SASL_BADPARAM;
-
-    if (creds.password == NULL) {
-        /*
-         * Pass the 0 third argument to nmh_get_credentials() so
-         * that the default password isn't used.  With legacy/.netrc
-         * credentials support, we'll only get here if the -user
-         * switch to send(1)/post(8) wasn't used.
-         */
-        if (nmh_get_credentials (p_context->host, p_context->user, 0, &creds)
-            != OK) {
-            return SASL_BADPARAM;
-        }
-    }
-
-    len = strlen (creds.password);
-
-    *psecret = (sasl_secret_t *) mh_xmalloc(sizeof(sasl_secret_t) + len);
-
-    (*psecret)->len = len;
-    strcpy((char *) (*psecret)->data, creds.password);
-
-    return SASL_OK;
-}
-#endif /* CYRUS_SASL */
-
-int
-pop_auth_xoauth(const char *client_res)
-{
-    char server_mechs[256];
-    int status = check_mech(server_mechs, sizeof(server_mechs), "XOAUTH");
-
-    if (status != OK) return status;
-
-    if ((status = command("AUTH XOAUTH2 %s", client_res)) != OK) {
-      return status;
-    }
-    if (strncmp(response, "+OK", 3) == 0) {
-       return OK;
-    }
-
-    /* response contains base64-encoded JSON, which is always the same.
-     * See mts/smtp/smtp.c for more notes on that. */
-    /* Then we're supposed to send an empty response ("\r\n"). */
-    return command("");
-}
-
 /*
  * Split string containing proxy command into an array of arguments
  * suitable for passing to exec. Returned array must be freed. Shouldn't
@@ -507,25 +135,23 @@ parse_proxy(char *proxy, char *host)
 
 int
 pop_init (char *host, char *port, char *user, char *pass, char *proxy,
-         int snoop, int sasl, char *mech, const char *oauth_svc)
+         int snoop, int sasl, char *mech, int tls, const char *oauth_svc)
 {
     int fd1, fd2;
     char buffer[BUFSIZ];
-    const char *xoauth_client_res = NULL;
-#ifndef CYRUS_SASL
-    NMH_UNUSED (sasl);
-    NMH_UNUSED (mech);
-#endif /* ! CYRUS_SASL */
+    char *errstr;
+
+    nsc = netsec_init();  
+
+    if (user)
+       netsec_set_userid(nsc, user);
 
-#ifdef OAUTH_SUPPORT
     if (oauth_svc != NULL) {
-       xoauth_client_res = mh_oauth_do_xoauth(user, oauth_svc,
-                                              snoop ? stderr : NULL);
+       if (netsec_set_oauth_service(nsc, oauth_svc) != OK) {
+           snprintf(response, sizeof(response), "OAuth2 not supported");
+           return NOTOK;
+       }
     }
-#else
-    NMH_UNUSED (oauth_svc);
-    NMH_UNUSED (xoauth_client_res);
-#endif /* OAUTH_SUPPORT */
 
     if (proxy && *proxy) {
        int pid;
@@ -575,47 +201,59 @@ pop_init (char *host, char *port, char *user, char *pass, char *proxy,
        fd2=outpipe[1];
 
     } else {
-
        if ((fd1 = client (host, port ? port : "pop3", response,
                           sizeof(response), snoop)) == NOTOK) {
            return NOTOK;
        }
+       fd2 = fd1;
+    }
 
-       if ((fd2 = dup (fd1)) == NOTOK) {
-           char *s;
+    SIGNAL (SIGPIPE, SIG_IGN);
+
+    netsec_set_fd(nsc, fd1, fd2);
+    netsec_set_snoop(nsc, snoop);
+
+    if (tls) {
+       if (netsec_set_tls(nsc, 1, &errstr) != OK) {
+           snprintf(response, sizeof(response), "%s", errstr);
+           free(errstr);
+           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);
+       if (netsec_negotiate_tls(nsc, &errstr) != OK) {
+           snprintf(response, sizeof(response), "%s", errstr);
+           free(errstr);
            return NOTOK;
        }
     }
-    if (pop_set (fd1, fd2, snoop) == NOTOK)
-       return NOTOK;
 
-    SIGNAL (SIGPIPE, SIG_IGN);
+    if (sasl) {
+       if (netsec_set_sasl_params(nsc, host, "pop", mech,
+                                  pop_sasl_callback, &errstr) != OK) {
+           snprintf(response, sizeof(response), "%s", errstr);
+           free(errstr);
+           return NOTOK;
+       }
+    }
 
-    switch (sasl_getline (response, sizeof response, input)) {
+    switch (pop_getline (response, sizeof response, nsc)) {
        case OK: 
            if (poprint)
                fprintf (stderr, "<--- %s\n", response);
            if (*response == '+') {
-#  ifdef CYRUS_SASL
                if (sasl) {
-                   if (pop_auth_sasl(user, host, mech) != NOTOK)
-                       return OK;
-               } else
-#  endif /* CYRUS_SASL */
-#  if OAUTH_SUPPORT
-               if (xoauth_client_res != NULL) {
-                   if (pop_auth_xoauth(xoauth_client_res) != NOTOK)
-                       return OK;
+                   char server_mechs[256];
+                   if (check_mech(server_mechs, sizeof(server_mechs)) != OK)
+                       return NOTOK;
+                   if (netsec_negotiate_sasl(nsc, server_mechs,
+                                             &errstr) != OK) {
+                       strncpy(response, errstr, sizeof(response));
+                       response[sizeof(response) - 1] = '\0';
+                       free(errstr);
+                       return NOTOK;
+                   }
+                   return OK;
                } else
-#  endif /* OAUTH_SUPPORT */
                if (command ("USER %s", user) != NOTOK
                    && command ("%s %s", (pophack++, "PASS"),
                                        pass) != NOTOK)
@@ -630,44 +268,190 @@ pop_init (char *host, char *port, char *user, char *pass, char *proxy,
        case DONE: 
            if (poprint)            
                fprintf (stderr, "%s\n", response);
-           fclose (input);
-           fclose (output);
+           netsec_shutdown(nsc, 1);
+           nsc = NULL;
            return NOTOK;
     }
 
     return NOTOK;      /* NOTREACHED */
 }
 
-int
-pop_set (int in, int out, int snoop)
+
+/*
+ * Our SASL callback; we are given SASL tokens and then have to format
+ * them according to the protocol requirements, and then process incoming
+ * messages and feed them back into the SASL library.
+ */
+
+static int
+pop_sasl_callback(enum sasl_message_type mtype, unsigned const char *indata,
+                 unsigned int indatalen, unsigned char **outdata,
+                 unsigned int *outdatalen, char **errstr)
 {
+    int rc, snoopoffset;
+    char *mech, *line;
+    size_t len, b64len;
 
-    if ((input = fdopen (in, "r")) == NULL
-           || (output = fdopen (out, "w")) == NULL) {
-       strncpy (response, "fdopen failed on connection descriptor", sizeof(response));
-       if (input)
-           fclose (input);
-       else
-           close (in);
-       close (out);
-       return NOTOK;
-    }
+    switch (mtype) {
+    case NETSEC_SASL_START:
+       /*
+        * Generate our AUTH message, but there is a wrinkle.
+        *
+        * Technically, according to RFC 5034, if your command INCLUDING
+        * an initial response exceeds 255 octets (including CRLF), you
+        * can't issue this all in one go, but have to just issue the
+        * AUTH command, wait for a blank initial response, and then
+        * send your data.
+        */
 
-    poprint = snoop;
+       mech = netsec_get_sasl_mechanism(nsc);
+
+       if (indatalen) {
+           char *b64data;
+           b64data = mh_xmalloc(BASE64SIZE(indatalen));
+           writeBase64raw(indata, indatalen, (unsigned char *) b64data);
+           b64len = strlen(b64data);
+
+           /* Formula here is AUTH + SP + mech + SP + out + CR + LF */
+           len = b64len + 8 + strlen(mech);
+           if (len > 255) {
+               rc = netsec_printf(nsc, errstr, "AUTH %s\r\n", mech);
+               if (rc)
+                   return NOTOK;
+               if (netsec_flush(nsc, errstr) != OK)
+                   return NOTOK;
+               line = netsec_readline(nsc, &len, errstr);
+               if (! line)
+                   return NOTOK;
+               /*
+                * If the protocol is being followed correctly, should just
+                * be a "+ ", nothing else.
+                */
+               if (len != 2 || strcmp(line, "+ ") != 0) {
+                   netsec_err(errstr, "Did not get expected blank response "
+                              "for initial challenge response");
+                   return NOTOK;
+               }
+               netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, NULL);
+               rc = netsec_printf(nsc, errstr, "%s\r\n", b64data);
+               netsec_set_snoop_callback(nsc, NULL, NULL);
+               free(b64data);
+               if (rc != OK)
+                   return NOTOK;
+               if (netsec_flush(nsc, errstr) != OK)
+                   return NOTOK;
+           } else {
+               netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder,
+                                         &snoopoffset);
+               snoopoffset = 6 + strlen(mech);
+               rc = netsec_printf(nsc, errstr, "AUTH %s %s\r\n", mech,
+                                  b64data);
+               free(b64data);
+               netsec_set_snoop_callback(nsc, NULL, NULL);
+               if (rc != OK)
+                   return NOTOK;
+               if (netsec_flush(nsc, errstr) != OK)
+                   return NOTOK;
+           }
+       } else {
+           if (netsec_printf(nsc, errstr, "AUTH %s\r\n", mech) != OK)
+               return NOTOK;
+           if (netsec_flush(nsc, errstr) != OK)
+               return NOTOK;
+       }
 
-    return OK;
-}
+       break;
 
+       /*
+        * We should get one line back, with our base64 data.  Decode that
+        * and feed it back into the SASL library.
+        */
+    case NETSEC_SASL_READ:
+       netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, &snoopoffset);
+       snoopoffset = 2;
+       line = netsec_readline(nsc, &len, errstr);
+       netsec_set_snoop_callback(nsc, NULL, NULL);
+
+       if (line == NULL)
+           return NOTOK;
+       if (len < 2 || (len == 2 && strcmp(line, "+ ") != 0)) {
+           netsec_err(errstr, "Invalid format for SASL response");
+           return NOTOK;
+       }
+
+       if (len == 2) {
+           *outdata = NULL;
+           *outdatalen = 0;
+       } else {
+           rc = decodeBase64(line + 2, outdata, &len, 0, NULL);
+           *outdatalen = len;
+           if (rc != OK) {
+               netsec_err(errstr, "Unable to decode base64 response");
+               return NOTOK;
+           }
+       }
+       break;
+
+    /*
+     * Our encoding is pretty simple, so this is easy.
+     */
+
+    case NETSEC_SASL_WRITE:
+       if (indatalen == 0) {
+           rc = netsec_printf(nsc, errstr, "\r\n");
+       } else {
+           unsigned char *b64data;
+           b64data = mh_xmalloc(BASE64SIZE(indatalen));
+           writeBase64raw(indata, indatalen, b64data);
+           netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, NULL);
+           rc = netsec_printf(nsc, errstr, "%s\r\n", b64data);
+           netsec_set_snoop_callback(nsc, NULL, NULL);
+           free(b64data);
+       }
+
+       if (rc != OK)
+           return NOTOK;
+
+       if (netsec_flush(nsc, errstr) != OK)
+           return NOTOK;
+       break;
+
+    /*
+     * Finish the protocol; we're looking for an +OK
+     */
+
+    case NETSEC_SASL_FINISH:
+       line = netsec_readline(nsc, &len, errstr);
+       if (line == NULL)
+           return NOTOK;
+
+       if (strncmp(line, "+OK", 3) != 0) {
+           netsec_err(errstr, "Authentication failed: %s", line);
+           return NOTOK;
+       }
+       break;
+
+    /*
+     * Cancel the SASL exchange in the middle of the commands; for
+     * POP, that's a single "*".
+     *
+     * It's unclear to me if I should be returning errors up; I finally
+     * decided the answer should be "yes", and if the upper layer wants to
+     * ignore them that's their choice.
+     */
+
+    case NETSEC_SASL_CANCEL:
+       rc = netsec_printf(nsc, errstr, "*\r\n");
+       if (rc == OK)
+           rc = netsec_flush(nsc, errstr);
+       if (rc != OK)
+           return NOTOK;
+       break;
+    }
 
-int
-pop_fd (char *in, int inlen, char *out, int outlen)
-{
-    snprintf (in, inlen, "%d", fileno (input));
-    snprintf (out, outlen, "%d", fileno (output));
     return OK;
 }
 
-
 /*
  * Find out number of messages available
  * and their total size.
@@ -749,7 +533,7 @@ pop_retr (int msgno, int (*action)(char *))
 static int
 traverse (int (*action)(char *), const char *fmt, ...)
 {
-    int result;
+    int result, snoopstate;
     va_list ap;
     char buffer[sizeof(response)];
 
@@ -761,13 +545,18 @@ traverse (int (*action)(char *), const char *fmt, ...)
        return NOTOK;
     strncpy (buffer, response, sizeof(buffer));
 
+    if ((snoopstate = netsec_get_snoop(nsc)))
+       netsec_set_snoop(nsc, 0);
+
     for (;;)
        switch (multiline ()) {
            case NOTOK: 
+               netsec_set_snoop(nsc, snoopstate);
                return NOTOK;
 
            case DONE: 
                strncpy (response, buffer, sizeof(response));
+               netsec_set_snoop(nsc, snoopstate);
                return OK;
 
            case OK: 
@@ -820,12 +609,8 @@ pop_quit (void)
 int
 pop_done (void)
 {
-#ifdef CYRUS_SASL
-    if (conn)
-       sasl_dispose(&conn);
-#endif /* CYRUS_SASL */
-    fclose (input);
-    fclose (output);
+    if (nsc)
+       netsec_shutdown(nsc, 1);
 
     return OK;
 }
@@ -848,36 +633,31 @@ command(const char *fmt, ...)
 static int
 vcommand (const char *fmt, va_list ap)
 {
-    char *cp, buffer[65536];
-
-    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;
-           fprintf (stderr, "---> %s ********\n", buffer);
-           if (cp)
-               *cp = ' ';
-           pophack = 0;
-       }
-       else
-           fprintf (stderr, "---> %s\n", buffer);
+    /* char *cp; */
+    char *errstr;
+
+    if (netsec_vprintf(nsc, &errstr, fmt, ap) != OK) {
+       strncpy(response, errstr, sizeof(response));
+       response[sizeof(response) - 1] = '\0';
+       free(errstr);
+       return NOTOK;
     }
 
-    if (putline (buffer, output) == NOTOK)
+    if (netsec_printf(nsc, &errstr, "\r\n") != OK) {
+       strncpy(response, errstr, sizeof(response));
+       response[sizeof(response) - 1] = '\0';
+       free(errstr);
        return NOTOK;
+    }
 
-#ifdef CYRUS_SASL
-    if (poprint && sasl_ssf)
-       fprintf(stderr, "(decrypted) ");
-#endif /* CYRUS_SASL */
+    if (netsec_flush(nsc, &errstr) != OK) {
+       strncpy(response, errstr, sizeof(response));
+       response[sizeof(response) - 1] = '\0';
+       free(errstr);
+       return NOTOK;
+    }
 
-    switch (sasl_getline (response, sizeof response, input)) {
+    switch (pop_getline (response, sizeof response, nsc)) {
        case OK: 
            if (poprint)
                fprintf (stderr, "<--- %s\n", response);
@@ -899,17 +679,8 @@ multiline (void)
 {
     char buffer[BUFSIZ + TRMLEN];
 
-    if (sasl_getline (buffer, sizeof buffer, input) != OK)
+    if (pop_getline (buffer, sizeof buffer, nsc) != OK)
        return NOTOK;
-#ifdef DEBUG
-    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 DONE;
@@ -923,158 +694,42 @@ multiline (void)
 }
 
 /*
- * Note that these functions have been modified to deal with layer encryption
- * in the SASL case
+ * This is now just a thin wrapper around netsec_readline().
  */
 
 static int
-sasl_getline (char *s, int n, FILE *iop)
+pop_getline (char *s, int n, netsec_context *ns)
 {
-    int c = -2;
+    /* int c = -2; */
     char *p;
+    size_t len, destlen;
+    /* int rc; */
+    char *errstr;
 
-    p = s;
-    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;
-    }
-    if (c == EOF && p == s) {
-       strncpy (response, "connection closed by foreign host", sizeof(response));
-       return DONE;
-    }
-    *p = 0;
-    if (*--p == '\n')
-       *p = 0;
-    if (p > s  &&  *--p == '\r')
-       *p = 0;
-
-    return OK;
-}
-
-
-static int
-putline (char *s, FILE *iop)
-{
-#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);
+    p = netsec_readline(ns, &len, &errstr);
 
-       if (result != SASL_OK) {
-           snprintf(response, sizeof(response), "SASL encoding error: %s",
-                    sasl_errdetail(conn));
-           return NOTOK;
-       }
-
-       if (fwrite(buf, buflen, 1, iop) < 1) {
-           advise ("putline", "fwrite");
-       }
-    }
-#endif /* CYRUS_SASL */
-
-    fflush (iop);
-    if (ferror (iop)) {
-       strncpy (response, "lost connection", sizeof(response));
+    if (p == NULL) {
+       strncpy(response, errstr, sizeof(response));
+       response[sizeof(response) - 1] = '\0';
+       free(errstr);
        return NOTOK;
     }
 
-    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 unsigned int size = 0;
-    static int cnt = 0;
-    unsigned int retbufsize = 0;
-    int cc, result;
-    char *retbuf, tmpbuf[SASL_BUFFER_SIZE];
-
-    /*
-     * 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.
+     * If we had an error, it should have been returned already.  Since
+     * netsec_readline() strips off the CR-LF ending, just copy the existing
+     * buffer into response now.
+     *
+     * We get a length back from netsec_readline, but the rest of the POP
+     * code doesn't handle it; the assumptions are that everything from
+     * the network can be respresented as C strings.  That should get fixed
+     * someday.
      */
 
-    while (retbufsize == 0) {
-
-       cc = read(fileno(f), tmpbuf, sizeof(tmpbuf));
+    destlen = len < ((size_t) (n - 1)) ? len : (size_t) (n - 1);
 
-       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];
+    memcpy(s, p, destlen);
+    s[destlen] = '\0';
+    
+    return OK;
 }
-#endif /* CYRUS_SASL */
index 47941af..db9c029 100644 (file)
@@ -83,7 +83,6 @@
     X("partno", -6, PARTSW) \
     X("sasl", SASLminc(4), SASLSW) \
     X("nosasl", SASLminc(6), NOSASLSW) \
-    X("saslmaxssf", SASLminc(10), SASLMXSSFSW) \
     X("saslmech", SASLminc(5), SASLMECHSW) \
     X("user", SASLminc(-4), USERSW) \
     X("port server submission port name/number", 4, PORTSW) \
@@ -231,7 +230,6 @@ 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 int saslssf=-1;         /* Our maximum SSF for SASL              */
 static char *saslmech=NULL;    /* Force use of particular SASL mech     */
 static char *user=NULL;                /* Authenticate as this user             */
 static char *port="submission";        /* Name of server port for SMTP submission */
@@ -463,12 +461,6 @@ main (int argc, char **argv)
                    sasl = 0;
                    continue;
 
-               case SASLMXSSFSW:
-                   if (!(cp = *argp++) || *cp == '-')
-                       adios (NULL, "missing argument to %s", argp[-2]);
-                   saslssf = atoi(cp);
-                   continue;
-               
                case SASLMECHSW:
                    if (!(saslmech = *argp++) || *saslmech == '-')
                        adios (NULL, "missing argument to %s", argp[-2]);
@@ -1655,8 +1647,7 @@ post (char *file, int bccque, int talk, int eai, char *envelope,
        }
     } else {
        if (rp_isbad (retval = sm_init (clientsw, serversw, port, watch,
-                                       verbose, snoop, sasl, saslssf,
-                                       saslmech, user,
+                                       verbose, snoop, sasl, saslmech, user,
                                        oauth_flag ? auth_svc : NULL, tls))
             || rp_isbad (retval = sm_winit (envelope, eai))) {
            die (NULL, "problem initializing server; %s", rp_string (retval));
@@ -1698,8 +1689,7 @@ verify_all_addresses (int talk, int eai, char *envelope, int oauth_flag,
 
     if (!whomsw || checksw) {
        if (rp_isbad (retval = sm_init (clientsw, serversw, port, watch,
-                                       verbose, snoop, sasl, saslssf,
-                                       saslmech, user,
+                                       verbose, snoop, sasl, saslmech, user,
                                        oauth_flag ? auth_svc : NULL, tls))
                || rp_isbad (retval = sm_winit (envelope, eai))) {
            die (NULL, "problem initializing server; %s", rp_string (retval));
index a491198..1e48ea3 100644 (file)
@@ -63,7 +63,6 @@
     X("snoop", 5, SNOOPSW) \
     X("sasl", SASLminc(4), SASLSW) \
     X("nosasl", SASLminc(6), NOSASLSW) \
-    X("saslmaxssf", SASLminc(6), SASLMXSSFSW) \
     X("saslmech mechanism", SASLminc(6), SASLMECHSW) \
     X("authservice", SASLminc(0), AUTHSERVICESW) \
     X("user username", SASLminc(-4), USERSW) \
@@ -287,7 +286,6 @@ main (int argc, char **argv)
                case WIDTHSW: 
                case CLIESW: 
                case SERVSW: 
-               case SASLMXSSFSW:
                case PORTSW:
                case MTSSM:
                case MTSSW:
index 80386e9..d6c336f 100644 (file)
@@ -947,7 +947,6 @@ buildfile (char **argp, char *file)
     X("nodraftfolder", 0, SNDRFSW) \
     X("sasl", SASLminc(4), SASLSW) \
     X("nosasl", SASLminc(6), NOSASLSW) \
-    X("saslmaxssf", SASLminc(10), SASLMXSSFSW) \
     X("saslmech", SASLminc(5), SASLMECHSW) \
     X("authservice", SASLminc(0), AUTHSERVICESW) \
     X("user username", SASLminc(4), USERSW) \
@@ -1165,7 +1164,6 @@ sendit (char *sp, char **arg, char *file, int pushed)
                case WIDTHSW:
                case CLIESW:
                case SERVSW:
-               case SASLMXSSFSW:
                case USERSW:
                case PORTSW:
                case MTSSM: