## 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"
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
##
##
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
##
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)
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)
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 \
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])
--- /dev/null
+#!/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 "$@"
#### 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,
- 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
--- /dev/null
+/*
+ * 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, ...);
* 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.
* 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 *);
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.
*
*/
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
* 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 **);
.IR mechanism ]
.RB [ \-authservice
.IR service ]
+.RB [ \-initialtls ]
+.RB [ \-notls ]
.RB [ \-snoop ]
.RB [ \-version ]
.RB [ \-help ]
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
.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
.IR portname/number ]
.RB [ \-sasl ]
.RB [ \-nosasl ]
-.RB [ \-saslmaxssf
-.IR ssf ]
.RB [ \-saslmech
.IR mechanism ]
.RB [ \-user
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
.IR port-name/number ]
.RB [ \-sasl ]
.RB [ \-nosasl ]
-.RB [ \-saslmaxssf
-.IR ssf ]
.RB [ \-saslmech
.IR mechanism ]
.RB [ \-authservice
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
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
.RB [ \-user
.IR username ]
.RB [ \-tls ]
+.RB [ \-initialtls ]
.RB [ \-notls ]
.RI [ file ]
.RB [ \-draft ]
.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
.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
#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 = "; ";
/*
* 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;
sm_verbose = verbose;
sm_debug = debug;
- if (sm_rfp != NULL && sm_wfp != NULL)
+ if (nsc != NULL)
goto send_options;
if (client == NULL || *client == '\0') {
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
*/
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:
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
* 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);
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
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);
}
}
}
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') {
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");
_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;
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");
{
int result;
- sm_alarmed = 0;
- alarm (SM_TEXT);
result = sm_wstream (buffer, len);
- alarm (0);
return (result == NOTOK ? RP_BHST : RP_OK);
}
}
}
- if (sm_rfp == NULL && sm_wfp == NULL)
+ if (nsc == NULL)
return RP_OK;
switch (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;
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;
}
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;
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
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;
}
} 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;
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;
}
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;
}
* 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);
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;
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) {
*/
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) {
* 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;
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);
--- /dev/null
+
+/*
+ * 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;
+}
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));
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
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
}
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() {
}
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() {
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'
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"
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'
# 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'
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).
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
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
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
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
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
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
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
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
EOF
test_post "${testname}.actual" "${testname}.expected" \
- > ${testname}.post 2>/dev/null
+ 2> ${testname}.post
check "${testname}.post-expected" "${testname}.post"
#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,
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;
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);
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;
* 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);
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);
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 == '-')
* 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 */
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;
/* 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)
msgnum == hghnum && chgflag, 1, NULL, 0L, noisy,
&scanl)) {
case SCNFAT:
- case SCNEOF:
+ case SCNEOF:
break;
case SCNERR:
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;
}
fclose (sf);
sf = NULL;
- }
+ }
if (pf == NULL && (pf = fopen (cp, "r")) == NULL)
adios (cp, "not available");
chmod (cp, m_gmprot ());
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;
/* 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.
*/
/* sbeck -- handle suffixes */
CI ci;
CE ce = &ct->c_cefile;
- const char *decoded;
+ unsigned char *decoded;
size_t decoded_len;
unsigned char digest[16];
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);
}
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) {
} else {
if (debugsw) {
fprintf (stderr, "invalid MD5 digest (got %d octets)\n",
- (int) strlen (digest));
+ (int) strlen ((char *) digest));
}
return NOTOK;
/* 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);
#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>
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
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;
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
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;
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)
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.
static int
traverse (int (*action)(char *), const char *fmt, ...)
{
- int result;
+ int result, snoopstate;
va_list ap;
char buffer[sizeof(response)];
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:
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;
}
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);
{
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;
}
/*
- * 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 */
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) \
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 */
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]);
}
} 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));
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));
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) \
case WIDTHSW:
case CLIESW:
case SERVSW:
- case SASLMXSSFSW:
case PORTSW:
case MTSSM:
case MTSSW:
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) \
case WIDTHSW:
case CLIESW:
case SERVSW:
- case SASLMXSSFSW:
case USERSW:
case PORTSW:
case MTSSM: