From: David Levine Date: Sun, 25 Sep 2016 13:28:05 +0000 (-0400) Subject: Merge remote-tracking branch 'origin' into smtputf8 X-Git-Url: https://diplodocus.org/git/nmh/commitdiff_plain/aaf014c77a4fb19bdc33370f5b6af5b8497decf8?hp=2c00d4488d86c136d4ef1dcd78cce0d23c91d736 Merge remote-tracking branch 'origin' into smtputf8 --- diff --git a/Makefile.am b/Makefile.am index ca46a4cd..cb1c39fb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -118,8 +118,8 @@ check_PROGRAMS = test/getfullname test/getcanon test/fakepop test/fakesmtp \ ## distcheck, $nmhetcdir. For distcheck, prepend $MH_INST_DIR (from ## test/common.sh.in), which is based on $MH_TEST_DIR (from ## $TESTS_ENVIRONMENT). -DISTCHECK_CONFIGURE_FLAGS = DISABLE_SETGID_MAIL=1 \ - NMHETCDIRINST='$${abs_builddir}/test/testdir/inst' +AM_DISTCHECK_CONFIGURE_FLAGS = DISABLE_SETGID_MAIL=1 \ + NMHETCDIRINST='$${abs_builddir}/test/testdir/inst' ## ## Stuff that should be cleaned via "make clean" @@ -198,9 +198,9 @@ noinst_LIBRARIES = sbr/libmh.a mts/libmts.a noinst_HEADERS = h/addrsbr.h h/aliasbr.h h/crawl_folders.h h/dropsbr.h \ h/fmt_compile.h h/fmt_scan.h h/icalendar.h h/md5.h h/mf.h \ h/mh.h h/mhcachesbr.h h/mhparse.h h/mime.h \ - h/mts.h h/nmh.h h/picksbr.h h/popsbr.h h/prototypes.h \ - h/rcvmail.h h/scansbr.h h/signals.h h/tws.h h/utils.h \ - mts/smtp/smtp.h sbr/ctype-checked.h h/oauth.h \ + h/mts.h h/nmh.h h/netsec.h h/picksbr.h h/popsbr.h \ + h/prototypes.h h/rcvmail.h h/scansbr.h h/signals.h h/tws.h \ + h/utils.h mts/smtp/smtp.h sbr/ctype-checked.h h/oauth.h \ thirdparty/jsmn/jsmn.h ## @@ -241,7 +241,8 @@ dist_doc_DATA = COPYRIGHT INSTALL NEWS README VERSION \ ## docs_contribdir = $(docdir)/contrib dist_docs_contrib_SCRIPTS = docs/contrib/replyfilter docs/contrib/build_nmh \ - docs/contrib/ml docs/contrib/vpick + docs/contrib/localpostproc docs/contrib/ml \ + docs/contrib/vpick docs/contrib/replaliases dist_docs_contrib_DATA = docs/contrib/replaliases ## @@ -343,7 +344,8 @@ uip_forw_SOURCES = uip/forw.c uip/whatnowproc.c uip/whatnowsbr.c uip/sendsbr.c \ uip_forw_LDADD = $(LDADD) $(READLINELIB) $(TERMLIB) $(ICONVLIB) $(POSTLINK) uip_inc_SOURCES = uip/inc.c uip/scansbr.c uip/dropsbr.c uip/popsbr.c -uip_inc_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(SASLLIB) $(CURLLIB) $(POSTLINK) +uip_inc_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(SASLLIB) $(CURLLIB) \ + $(TLSLIB) $(POSTLINK) uip_install_mh_SOURCES = uip/install-mh.c uip_install_mh_LDADD = $(LDADD) $(POSTLINK) @@ -390,7 +392,7 @@ uip_mhstore_SOURCES = uip/mhstore.c uip/mhparse.c uip/mhcachesbr.c \ uip_mhstore_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(POSTLINK) uip_msgchk_SOURCES = uip/msgchk.c uip/popsbr.c -uip_msgchk_LDADD = $(LDADD) $(SASLLIB) $(CURLLIB) $(POSTLINK) +uip_msgchk_LDADD = $(LDADD) $(SASLLIB) $(CURLLIB) $(TLSLIB) $(POSTLINK) uip_new_SOURCES = uip/new.c uip_new_LDADD = $(LDADD) $(POSTLINK) @@ -610,7 +612,8 @@ sbr_libmh_a_SOURCES = sbr/addrsbr.c sbr/ambigsw.c sbr/atooi.c sbr/arglist.c \ sbr/m_draft.c sbr/m_getfld.c sbr/m_gmprot.c \ sbr/m_maildir.c sbr/m_name.c sbr/m_popen.c sbr/m_rand.c \ sbr/makedir.c sbr/md5.c sbr/message_id.c \ - sbr/mime_type.c sbr/mts.c sbr/norm_charmap.c sbr/path.c \ + sbr/mime_type.c sbr/mts.c sbr/netsec.c \ + sbr/norm_charmap.c sbr/path.c \ sbr/peekc.c sbr/pidwait.c sbr/pidstatus.c \ sbr/print_help.c sbr/print_sw.c sbr/print_version.c \ sbr/push.c sbr/putenv.c sbr/refile.c sbr/remdir.c \ diff --git a/configure.ac b/configure.ac index f1dc37bf..d3fa76ec 100644 --- a/configure.ac +++ b/configure.ac @@ -469,12 +469,12 @@ dnl ----------------- AS_IF([test x"$tls_support" = x"yes"],[ dnl OpenBSD 5 needs the other-libraries (fourth argument) to the - dnl AC_CHECK_LIB for SSL_library_init, because it doesn't + dnl AC_CHECK_LIB for SSL_new, because it doesn't dnl automatically append -lcrypto when linking with -lssl. AC_CHECK_HEADER([openssl/ssl.h], , [AC_MSG_ERROR([openssl/ssl.h not found])]) AC_CHECK_LIB([crypto], [BIO_write], [TLSLIB="-lcrypto"], [AC_MSG_ERROR([OpenSSL crypto library not found])]) - AC_CHECK_LIB([ssl], [SSL_library_init], [TLSLIB="-lssl $TLSLIB"], + AC_CHECK_LIB([ssl], [SSL_new], [TLSLIB="-lssl $TLSLIB"], [AC_MSG_ERROR([OpenSSL library not found])],[$TLSLIB])], [TLSLIB=]) AC_SUBST([TLSLIB]) diff --git a/docs/contrib/localpostproc b/docs/contrib/localpostproc new file mode 100755 index 00000000..c8f71702 --- /dev/null +++ b/docs/contrib/localpostproc @@ -0,0 +1,89 @@ +#!/bin/sh +# +# localpostproc - A sample postproc which changes the submission email server +# based on user-supplied criteria. +# +# The basic concept is that we change where we submit mail to based on the +# message contents. We use scan(1) to get out fields we care about. But +# really, you could use ANY criteria, such as environment variables, +# recipients, etc etc. +# + +# +# Find out which message is the draft message; yes, this sucks. +# +# The case statement has to know about switches that take arguments; +# add to this list as necessary. +# + +whom=0 + +find_draftmessage() { + while test $# -gt 0; do + case "$1" in + -al* | -filt* | -wi* | -client | -idanno | -server | \ + -partno | -saslmech | -user | -por* | -width | \ + -file* | -mhl* | -mt* | -cr* | -lib* | -auth* | -sendmail) + shift + ;; + -whom) + whom=1 + ;; + -*) ;; + *) + draftmessage="$1" + return 0 + ;; + esac + shift + done + + echo "Cannot find draft message name in argument list" + exit 1 +} + +realpost="$(mhparam libdir)/post" + +if [ $# -eq 0 ]; then + echo "Usage: [post switches] filename" + exit 1 +fi + +find_draftmessage "$@" + +if [ $whom -eq 1 ]; then + exec "$realpost" "$@" +fi + +fromhost=$(scan -format '%<{resent-from}%(host{resent-from})%|%(host{from})%>' -file "$draftmessage") + +if [ $? -ne 0 ]; then + echo "Unable to run scan on draft file $draftmessage, aborting" + exit 1 +fi + +if [ -z "$fromhost" ]; then + echo "Could not determine hostname of From: address" + exit 1; +fi + +# +# Here we use the hostname in the "from" address, but you could use anything +# + +case "$fromhost" in + *host1.com) + postflags="-server smtp.host1.com -sasl -port submission" + ;; + + host2.com) + postflags="-server smtp.host2.com -sasl -tls -port submission" + ;; + + *) + echo "Don't know how to send email from $fromhost" + exit 1 + ;; +esac + +exec "$realpost" $postflags "$@" diff --git a/docs/contrib/replaliases b/docs/contrib/replaliases index 6848dc23..796e7b2b 100644 --- a/docs/contrib/replaliases +++ b/docs/contrib/replaliases @@ -1,7 +1,13 @@ #### replaliases #### #### convenience functions for various repl(1) commands -#### They're functions instead of aliases for portability. +#### +#### They're functions instead of aliases for portability. This file +#### is intended to be sourced from a Bourne-compatible shell, e.g., +#### source `mhparam docdir`/contrib/replaliases +#### to declare the functions. +#### +#### Author: David Levine #### If using par (see mhn.defaults), it helps to have its PARINIT #### environment variable set. If you really want it to be null, diff --git a/docs/pending-release-notes b/docs/pending-release-notes index 70ed4ff6..f44eb066 100644 --- a/docs/pending-release-notes +++ b/docs/pending-release-notes @@ -48,8 +48,10 @@ NEW FEATURES - post(8) -snoop now attempts to decode base64-encoded SMTP traffic. - folder(1) -nocreate now prints a warning message for a non-existant folder. - mhfixmsg(1) now allows -decodetext binary, though 8bit is still the default. +- inc(1) and msgchk(1) now support TLS encryption natively. - Support for SMTPUTF8 (RFC 6531) has been added. mhshow(1) already supported - RFC 6532, assuming all 8-bit message header field bodies are UTF-8. + RFC 6532, assuming all 8-bit message header field bodies are UTF-8 and use + of a UTF-8 locale. ----------------- OBSOLETE FEATURES diff --git a/h/netsec.h b/h/netsec.h new file mode 100644 index 00000000..e05e79f1 --- /dev/null +++ b/h/netsec.h @@ -0,0 +1,405 @@ +/* + * Network security library routines for nmh. + * + * These are a common set of routines to handle network security for + * things like SASL and OpenSSL. + */ + +struct _netsec_context; +typedef struct _netsec_context netsec_context; + +/* + * Create a network security context. Returns the allocated network + * security context. Cannot fail. + */ + +netsec_context *netsec_init(void); + +/* + * Shuts down the security context for a connection and frees all + * associated resources. + * + * Arguments: + * + * ns_context - Network security context + * closeflag - If set to 1, close the socket descriptor as well. + */ + +void netsec_shutdown(netsec_context *ns_context, int closeflag); + +/* + * Sets the file descriptor for this connection. This will be used by + * the underlying communication. + * + * Arguments: + * + * ns_context - Network security context + * readfd - Read file descriptor of remote connection. + * writefd - Write file descriptor of remote connection + */ + +void netsec_set_fd(netsec_context *ns_context, int readfd, int writefd); + +/* + * Set the userid used to authenticate to this connection. + * + * Arguments: + * + * ns_context - Network security context + * userid - Userid to be used for authentication. Cannot be NULL. + */ + +void netsec_set_userid(netsec_context *ns_context, const char *userid); + +/* + * Returns "snoop" status on current connection. + * + * Arguments: + * + * ns_context - Network security context + * + * Returns "1" if snoop is enabled, 0 if it is not. + */ + +int netsec_get_snoop(netsec_context *ns_context); + +/* + * Sets "snoop" status; if snoop is set to a nonzero value, network traffic + * will be logged on standard error. + * + * Arguments: + * + * ns_context - Network security context + * snoop - Integer value; set to nonzero to enable traffic logging + */ + +void netsec_set_snoop(netsec_context *ns_context, int snoop); + +/* + * A callback designed to handle the snoop output; it can be used by + * a protocol to massage the data in a more user-friendly way. + * + * Arguments: + * + * ns_context - Network security context + * string - String to output + * len - Length of string + * context - "Extra" context information to be used by callback. + */ + +typedef void (netsec_snoop_callback)(netsec_context *ns_context, + const char *string, size_t len, + void *context); + +/* + * Set the snoop callback function; will be used to handle protocol-specific + * messages. Set to NULL to disable. + * + * Arguments: + * + * ns_context - Network security context + * callback - Snoop callback + * context - Extra context information to be passed to callback. + */ + +void netsec_set_snoop_callback(netsec_context *ns_context, + netsec_snoop_callback *callback, void *context); + +/* + * A sample callback protocols can utilize; decode base64 tokens in the + * output. The context is a pointer to an int which contains an offset + * into the data to start decoding. + */ + +extern netsec_snoop_callback netsec_b64_snoop_decoder; + +/* + * Set the read timeout for this connection. + * + * Arguments: + * + * ns_context - Network security context + * timeout - Read timeout, in seconds. + */ + +void netsec_set_timeout(netsec_context *ns_context, int timeout); + +/* + * Read a "line" from the network. This reads one CR/LF terminated line. + * Returns a pointer to a NUL-terminated string. This memory is valid + * until the next call to any read function. Will return an error if + * the line does not terminate with a CR/LF. + * + * Arguments: + * + * ns_context - Network security context + * length - Returned length of string + * errstr - Error string + * + * Returns pointer to string, or NULL on error. + */ + +char *netsec_readline(netsec_context *ns_context, size_t *length, + char **errstr); + +/* + * Read bytes from the network. + * + * Arguments: + * + * ns_context - Network security context + * buffer - Read buffer + * size - Buffer size + * errstr - Error size + * + * Returns number of bytes read, or -1 on error. + */ + +ssize_t netsec_read(netsec_context *ns_context, void *buffer, size_t size, + char **errstr); + +/* + * Write data to the network; if encryption is being performed, we will + * do it. Data may be buffered; use netsec_flush() to flush any outstanding + * data to the network. + * + * Arguments: + * + * ns_context - Network security context + * buffer - Output buffer to write to network + * size - Size of data to write to network + * errstr - Error string + * + * Returns OK on success, NOTOK otherwise. + */ + +int netsec_write(netsec_context *ns_context, const void *buffer, size_t size, + char **errstr); + +/* + * Write bytes using printf formatting + * + * Arguments: + * + * ns_context - Network security context + * errstr - Error string + * format - Format string + * ... - Arguments for format string + * + * Returns OK on success, NOTOK on error. + */ + +int netsec_printf(netsec_context *ns_context, char **errstr, + const char *format, ...); + +/* + * Write bytes using a va_list argument. + * + * Arguments: + * + * ns_context - Network security context + * errstr - Error string + * format - Format string + * ap - stdarg list. + * + * Returns OK on success, NOTOK on error. + */ + +int netsec_vprintf(netsec_context *ns_context, char **errstr, + const char *format, va_list ap); + +/* + * Flush any buffered bytes to the network. + * + * Arguments: + * + * ns_context - Network security context + * errstr - Error string + * + * Returns OK on success, NOTOK on error. + */ + +int netsec_flush(netsec_context *ns_context, char **errstr); + +/* + * Enumerated types for the type of message we are sending/receiving. + */ + +enum sasl_message_type { + NETSEC_SASL_START, /* Start of SASL authentication */ + NETSEC_SASL_READ, /* Reading a message */ + NETSEC_SASL_WRITE, /* Writing a message */ + NETSEC_SASL_FINISH, /* Complete SASL exchange */ + NETSEC_SASL_CANCEL /* Cancel a SASL exchange */ +}; + +/* + * The SASL callback; this is designed to parse a protocol-specific + * message and return the SASL protocol message back. + * + * The meaning of the arguments to the callback depend on the mtype + * arguments. See below for more detail. + * + * Arguments: + * + * mtype - The type of message we are processing (read/write/cancel). + * indata - A pointer to any input data. + * indatasize - The size of the input data in bytes + * outdata - Output data (freed by caller) + * outdatasize - Size of output data + * errstr - An error string to be returned (freed by caller). + * + * As a general note, plugins should perform their own I/O. Buffers returned + * by NETSEC_SASL_READ should be allocated by the plugins and will be freed + * by the netsec package. Error messages returned should be created by + * netsec_err(). + * + * Parameter interpretation based on mtype value: + * + * NETSEC_SASL_START - Create a protocol message that starts SASL + * authentication. If an initial response is + * supported, indata and indatasize will contain it. + * Otherwise they will be set to NULL and 0. + * NETSEC_SASL_READ - Parse and decode a protocol message and extract + * out the SASL payload data. indata will be set + * to NULL; the callback must read in the necessary + * data using the appropriate netsec function. + * outdata/outdatasize should contain the decoded + * SASL message (again, must be free()d by the caller). + * NETSEC_SASL_WRITE - Generate a protocol message to send over the + * network. indata/indatasize will contain the + * SASL payload data. + * NETSEC_SASL_FINISH - Process the final SASL message exchange; at + * this point SASL exchange should have completed + * and we should get a message back from the server + * telling us whether or not authentication is + * successful. All buffer parameters are NULL. + * NETSEC_SASL_CANCEL - Generate a protocol message that cancels the + * SASL protocol exchange; outdata/outdatasize + * should contain this message. + * + * The callback should return OK on success, NOTOK on failure. Depending + * at the point of the authentication exchange, the callback may be asked + * to generate a cancel message. + */ + +typedef int (*netsec_sasl_callback)(enum sasl_message_type mtype, + unsigned const char *indata, + unsigned int indatasize, + unsigned char **outdata, + unsigned int *outdatasize, char **errstr); + +/* + * Sets the SASL parameters for this connection. If this function is + * not called or is called with NULL for arguments, SASL authentication + * will not be attempted for this connection. + * + * The caller must provide a callback to parse the protocol and return + * the SASL protocol messages (see above for callback details). + * + * Arguments: + * + * ns_context - Network security context + * hostname - Fully qualified hostname of remote host. + * service - Service name (set to NULL to disable SASL). + * mechanism - The mechanism desired by the user. If NULL, the SASL + * library will attempt to negotiate the best mechanism. + * callback - SASL protocol callbacks + * errstr - Error string. + * + * Returns NOTOK if SASL is not supported. + */ + +int netsec_set_sasl_params(netsec_context *ns_context, const char *hostname, + const char *service, const char *mechanism, + netsec_sasl_callback callback, char **errstr); + +/* + * Start SASL negotiation. The Netsec library will use the callbacks + * supplied in netsec_set_sasl_params() to format and parse the protocol + * messages. + * + * Arguments: + * + * ns_context - Network security context + * mechlist - Space-separated list of supported SASL mechanisms + * errstr - An error string to be returned upon error. + * + * Returns OK on success, NOTOK on failure. + */ + +int netsec_negotiate_sasl(netsec_context *ns_context, const char *mechlist, + char **errstr); + +/* + * Returns the chosen SASL mechanism by the SASL library or netsec. + * + * Arguments: + * + * ns_context - Network security context + * + * Returns a string containing the chosen mech, or NULL if SASL is not + * supported or in use. + */ + +char *netsec_get_sasl_mechanism(netsec_context *ns_context); + +/* + * Set the OAuth service name used to retrieve the OAuth parameters from + * user's profile. Just calling this function is not enough to guarantee + * that XOAUTH2 authentication will be performed; the appropriate mechanism + * name must be passed into netsec_set_sasl_params(). + * + * Arguments: + * + * ns_context - Network security context + * service - OAuth2 service names. + * + * Returns NOTOK if OAuth2 is not supported. + */ + +int netsec_set_oauth_service(netsec_context *ns_context, const char *service); + +/* + * Controls whether or not TLS will be negotiated for this connection. + * + * Note: callers still have to call netsec_tls_negotiate() to start + * TLS negotiation at the appropriate point in the protocol. + * + * Arguments + * + * tls - If nonzero, enable TLS. Otherwise disable TLS + * negotiation. + * + * Returns NOTOK if TLS is not supported or was unable to initialize. + */ + +int netsec_set_tls(netsec_context *context, int tls, char **errstr); + +/* + * Start TLS negotiation on this protocol. This connection should have + * netsec_set_tls() called on it. + * + * Arguments: + * + * ns_context - Network security context + * errstr - Error string upon failure. + * + * Returns OK on success, NOTOK on failure. + */ + +int netsec_negotiate_tls(netsec_context *ns_context, char **errstr); + +/* + * Allocate and format an error string; should be used by plugins + * to report errors. + * + * Arguments: + * + * errstr - Error string to be returned + * format - printf(3) format string + * ... - Arguments to printf(3) + * + */ + +void netsec_err(char **errstr, const char *format, ...); diff --git a/h/oauth.h b/h/oauth.h index 274ca9de..0278cae7 100644 --- a/h/oauth.h +++ b/h/oauth.h @@ -105,14 +105,18 @@ struct mh_oauth_service_info { * Do the complete dance for XOAUTH2 as used by POP3 and SMTP. * * Load tokens for svc from disk, refresh if necessary, and return the - * base64-encoded client response. + * client response in client_response and client_response_len. * * If refreshing, writes freshened tokens to disk. * * Exits via adios on any error. + * + * Always returns OK for now, but in the future could return NOTOK on error. */ -char * -mh_oauth_do_xoauth(const char *user, const char *svc, FILE *log); + +int +mh_oauth_do_xoauth(const char *user, const char *svc, unsigned char **oauth_res, + size_t *oauth_res_len, FILE *log); /* * Allocate and initialize a new OAuth context. diff --git a/h/popsbr.h b/h/popsbr.h index 3fb4179d..c155fd12 100644 --- a/h/popsbr.h +++ b/h/popsbr.h @@ -3,7 +3,7 @@ * popsbr.h -- header for POP client subroutines */ -int pop_init (char *, char *, char *, char *, char *, int, int, char *, +int pop_init (char *, char *, char *, char *, char *, int, int, char *, int, const char *); int pop_fd (char *, int, char *, int); int pop_stat (int *, int *); diff --git a/h/prototypes.h b/h/prototypes.h index 5c1b5cf1..bc5b6a56 100644 --- a/h/prototypes.h +++ b/h/prototypes.h @@ -457,6 +457,10 @@ int what_now (char *, int, int, char *, char *, int, struct msgs *, char *, int, char *, int); int WhatNow(int, char **); +/* Includes trailing NUL */ + +#define BASE64SIZE(x) ((((x + 2) / 3) * 4) + 1) + /* * Copy data from one file to another, converting to base64-encoding. * @@ -470,8 +474,8 @@ int WhatNow(int, char **); */ int writeBase64aux(FILE *in, FILE *out, int crlf); -int writeBase64 (unsigned char *, size_t, unsigned char *); -int writeBase64raw (unsigned char *, size_t, unsigned char *); +int writeBase64 (const unsigned char *, size_t, unsigned char *); +int writeBase64raw (const unsigned char *, size_t, unsigned char *); /* * first argument: the string to be decoded @@ -480,7 +484,8 @@ int writeBase64raw (unsigned char *, size_t, unsigned char *); * fourth argument: non-zero for text content, and for which CR's should be skipped * fifth argument: for an MD5 digest, it can be null */ -int decodeBase64 (const char *, const char **, size_t *, int, unsigned char *); +int decodeBase64 (const char *, unsigned char **, size_t *, int, + unsigned char *); void hexify (const unsigned char *, size_t, char **); diff --git a/man/inc.man b/man/inc.man index b8a92454..c6e56184 100644 --- a/man/inc.man +++ b/man/inc.man @@ -39,6 +39,8 @@ inc \- incorporate new mail .IR mechanism ] .RB [ \-authservice .IR service ] +.RB [ \-initialtls ] +.RB [ \-notls ] .RB [ \-snoop ] .RB [ \-version ] .RB [ \-help ] @@ -276,8 +278,10 @@ for its other features. If .B nmh has been compiled with OAuth support, the +.B \-sasl +and .B \-saslmech xoauth2 -switch will enable OAuth authentication. The +switches will enable OAuth authentication. The .B \-user switch must be used, and the .I user-name @@ -291,10 +295,21 @@ and grant authorization to that account. See the .B mhlogin man page for more details. .PP -Gmail only supports POP3 over TLS, but -.B inc -has no TLS support. To work around this, use something like -.B -proxy 'openssl s_client -connect %h:995 -CAfile /etc/ssl/certs/ca-certificates.crt -quiet' +If +.B nmh +has been compiled with TLS support, the +.B \-initialtls +switch will require the negotiation of TLS when connecting +to the remote POP server. The +.B \-initialtls +switch will negotiate TLS immediately after the connection has taken place, +before any POP commands are sent or received. Data encrypted by TLS is +labeled `(tls-encrypted)' and `(tls-decrypted)` with viewing the POP +transaction with the +.B \-snoop +switch. The +.B \-notls +switch will disable all attempts to negotiate TLS. .SH FILES .PD 0 .TP 20 diff --git a/man/post.man b/man/post.man index 1023676a..55d63b89 100644 --- a/man/post.man +++ b/man/post.man @@ -32,8 +32,6 @@ post \- deliver a message .IR portname/number ] .RB [ \-sasl ] .RB [ \-nosasl ] -.RB [ \-saslmaxssf -.IR ssf ] .RB [ \-saslmech .IR mechanism ] .RB [ \-user @@ -236,11 +234,6 @@ switch. Base64-encoded data is wrapped with `b64<>'. (Beware that the SMTP transaction may contain authentication information either in plaintext or easily decoded base64.) -The -.B \-saslmaxssf -switch can be used to select the maximum value of the Security Strength Factor. -This is an integer value and the exact meaning of this value depends on the -underlying SASL mechanism. A value of 0 disables encryption. .PP If .B nmh diff --git a/man/send.man b/man/send.man index 680f1d46..5a2f3139 100644 --- a/man/send.man +++ b/man/send.man @@ -40,8 +40,6 @@ send \- send a message .IR port-name/number ] .RB [ \-sasl ] .RB [ \-nosasl ] -.RB [ \-saslmaxssf -.IR ssf ] .RB [ \-saslmech .IR mechanism ] .RB [ \-authservice @@ -427,17 +425,14 @@ switch; see the man page description of .B \-snoop for its other features. -The -.B \-saslmaxssf -switch can be used to select the maximum value of the Security Strength Factor. -This is an integer value and the exact meaning of this value depends on the -underlying SASL mechanism. A value of 0 disables encryption. .PP If .B nmh has been compiled with OAuth support, the +.B \-sasl +and .B \-saslmech xoauth2 -switch will enable OAuth authentication. The +switches will enable OAuth authentication. The .B \-user switch must be used, and the .I user-name @@ -448,7 +443,7 @@ be specified with the switch. Before using this, the user must authorize nmh by running .B mhlogin and grant authorization to that account. See the -.B mhlogin +.IR mhlogin (1) man page for more details. .PP If diff --git a/man/whom.man b/man/whom.man index cbad25f5..2e992e77 100644 --- a/man/whom.man +++ b/man/whom.man @@ -29,6 +29,7 @@ whom \- report to whom a message would go .RB [ \-user .IR username ] .RB [ \-tls ] +.RB [ \-initialtls ] .RB [ \-notls ] .RI [ file ] .RB [ \-draft ] @@ -106,16 +107,13 @@ switch; see the .B post man page description of .B \-snoop -for its other features. The -.B \-saslmaxssf -switch can be used to select the maximum value of the Security Strength Factor. -This is an integer value and the exact meaning of this value depends on the -underlying SASL mechanism. A value of 0 disables encryption. +for its other features. .PP If .B nmh has been compiled with TLS support, the -.B \-tls +.BR \-tls , +.BR \-initialtls , and .B \-notls switches will require and disable the negotiation of TLS support when connecting to the @@ -126,7 +124,7 @@ switch; see the .B post man page description of .B \-snoop -for its other features. +and the TLS flags for more details. .PP The files specified by the profile entry \*(lqAliasfile:\*(rq and any additional alias files given by the diff --git a/mts/smtp/smtp.c b/mts/smtp/smtp.c index 47215a8b..b5e68fa1 100644 --- a/mts/smtp/smtp.c +++ b/mts/smtp/smtp.c @@ -11,28 +11,9 @@ #include #include #include -#include - -#ifdef CYRUS_SASL -#include -#include -# 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 -#include -#include -#include -#endif /* CYRUS_SASL */ +#include -#ifdef TLS_SUPPORT -#include -#include -#endif /* TLS_SUPPORT */ +#include /* * This module implements an interface to SendMail very similar @@ -80,66 +61,11 @@ #define SM_AUTH 45 static int sm_addrs = 0; -static int sm_alarmed = 0; static int sm_child = NOTOK; static int sm_debug = 0; static int sm_nl = TRUE; static int sm_verbose = 0; - -static FILE *sm_rfp = NULL; -static FILE *sm_wfp = NULL; - -static int next_line_encoded = 0; - -#ifdef CYRUS_SASL -/* - * Some globals needed by SASL - */ - -static sasl_conn_t *conn = NULL; /* SASL connection state */ -static int sasl_complete = 0; /* Has authentication succeeded? */ -static sasl_ssf_t sasl_ssf; /* Our security strength factor */ -static int maxoutbuf; /* Maximum crypto output buffer */ -static char *sasl_outbuffer; /* SASL output buffer for encryption */ -static int sasl_outbuflen; /* Current length of data in outbuf */ -static int sm_get_user(void *, int, const char **, unsigned *); -static int sm_get_pass(sasl_conn_t *, void *, int, sasl_secret_t **); - -static sasl_callback_t callbacks[] = { - { SASL_CB_USER, (sasl_callback_ft) sm_get_user, NULL }, -#define SM_SASL_N_CB_USER 0 - { SASL_CB_AUTHNAME, (sasl_callback_ft) sm_get_user, NULL }, -#define SM_SASL_N_CB_AUTHNAME 1 - { SASL_CB_PASS, (sasl_callback_ft) sm_get_pass, NULL }, -#define SM_SASL_N_CB_PASS 2 - { SASL_CB_LIST_END, NULL, NULL }, -}; - -#else /* CYRUS_SASL */ -int sasl_ssf = 0; -#endif /* CYRUS_SASL */ - -#ifdef TLS_SUPPORT -static SSL_CTX *sslctx = NULL; -static SSL *ssl = NULL; -static BIO *sbior = NULL; -static BIO *sbiow = NULL; -static BIO *io = NULL; - -static int tls_negotiate(void); -#endif /* TLS_SUPPORT */ - -#if defined(CYRUS_SASL) || defined(TLS_SUPPORT) -#define SASL_MAXRECVBUF 65536 -static int sm_fgetc(FILE *); -static char *sasl_inbuffer; /* SASL input buffer for encryption */ -static char *sasl_inptr; /* Pointer to current inbuf position */ -static int sasl_inbuflen; /* Current length of data in inbuf */ -#else -#define sm_fgetc fgetc -#endif - -static int tls_active = 0; +static netsec_context *nsc = NULL; static char *sm_noreply = "No reply text given"; static char *sm_moreply = "; "; @@ -153,65 +79,42 @@ static char *EHLOkeys[MAXEHLO + 1]; /* * static prototypes */ -static int smtp_init (char *, char *, char *, int, int, int, int, int, - char *, char *, const char *, int); -static int sendmail_init (char *, char *, int, int, int, int, int, - char *, char *); +static int smtp_init (char *, char *, char *, int, int, int, int, const char *, + const char *, const char *, int); +static int sendmail_init (char *, int, int, int, int, const char *, + const char *); static int rclient (char *, char *); -static int sm_ierror (char *fmt, ...); +static int sm_ierror (const char *fmt, ...); +static int sm_nerror (char *); static int smtalk (int time, char *fmt, ...); -static int sm_wrecord (char *, int); static int sm_wstream (char *, int); -static int sm_werror (void); static int smhear (void); -static int sm_rrecord (char *, int *); -static int sm_rerror (int); -static void alrmser (int); static char *EHLOset (char *); -static char *prepare_for_display (const char *, int *); -static int sm_fwrite(char *, int); -static int sm_fputs(char *); -static int sm_fputc(int); -static void sm_fflush(void); -static int sm_fgets(char *, int, FILE *); -static int sm_auth_xoauth2(const char *, const char *, int); - -#ifdef CYRUS_SASL -/* - * Function prototypes needed for SASL - */ - -static int sm_auth_sasl(char *, int, char *, char *); -#endif /* CYRUS_SASL */ +static int sm_sasl_callback(enum sasl_message_type, unsigned const char *, + unsigned int, unsigned char **, unsigned int *, + char **); int sm_init (char *client, char *server, char *port, int watch, int verbose, - int debug, int sasl, int saslssf, char *saslmech, char *user, + int debug, int sasl, const char *saslmech, const char *user, const char *oauth_svc, int tls) { if (sm_mts == MTS_SMTP) return smtp_init (client, server, port, watch, verbose, - debug, sasl, saslssf, saslmech, user, - oauth_svc, tls); + debug, sasl, saslmech, user, oauth_svc, tls); else - return sendmail_init (client, server, watch, verbose, - debug, sasl, saslssf, saslmech, user); + return sendmail_init (client, watch, verbose, debug, sasl, + saslmech, user); } static int smtp_init (char *client, char *server, char *port, int watch, int verbose, - int debug, - int sasl, int saslssf, char *saslmech, char *user, + int debug, int sasl, const char *saslmech, const char *user, const char *oauth_svc, int tls) { - int result, sd1, sd2; -#ifndef CYRUS_SASL - NMH_UNUSED (sasl); - NMH_UNUSED (saslssf); - NMH_UNUSED (saslmech); - NMH_UNUSED (user); -#endif /* CYRUS_SASL */ + int result, sd1; + char *errstr; if (watch) verbose = TRUE; @@ -219,7 +122,7 @@ smtp_init (char *client, char *server, char *port, int watch, int verbose, sm_verbose = verbose; sm_debug = debug; - if (sm_rfp != NULL && sm_wfp != NULL) + if (nsc != NULL) goto send_options; if (client == NULL || *client == '\0') { @@ -237,35 +140,37 @@ smtp_init (char *client, char *server, char *port, int watch, int verbose, if (client == NULL || *client == '\0') client = "localhost"; -#if defined(CYRUS_SASL) || defined(TLS_SUPPORT) - sasl_inbuffer = malloc(SASL_MAXRECVBUF); - if (!sasl_inbuffer) - return sm_ierror("Unable to allocate %d bytes for read buffer", - SASL_MAXRECVBUF); -#endif /* CYRUS_SASL || TLS_SUPPORT */ + nsc = netsec_init(); - if ((sd1 = rclient (server, port)) == NOTOK) - return RP_BHST; + if (user) + netsec_set_userid(nsc, user); + + if (sm_debug) + netsec_set_snoop(nsc, 1); - if ((sd2 = dup (sd1)) == NOTOK) { - close (sd1); - return sm_ierror ("unable to dup"); + if (sasl) { + if (netsec_set_sasl_params(nsc, server, "smtp", saslmech, + sm_sasl_callback, &errstr) != OK) + return sm_nerror(errstr); + } + + if (oauth_svc) { + if (netsec_set_oauth_service(nsc, oauth_svc) != OK) + return sm_ierror("OAuth2 not supported"); } - SIGNAL (SIGALRM, alrmser); + if ((sd1 = rclient (server, port)) == NOTOK) + return RP_BHST; + SIGNAL (SIGPIPE, SIG_IGN); - if ((sm_rfp = fdopen (sd1, "r")) == NULL - || (sm_wfp = fdopen (sd2, "w")) == NULL) { - close (sd1); - close (sd2); - sm_rfp = sm_wfp = NULL; - return sm_ierror ("unable to fdopen"); - } + netsec_set_fd(nsc, sd1, sd1); - tls_active = 0; + if (tls) { + if (netsec_set_tls(nsc, 1, &errstr) != OK) + return sm_nerror(errstr); + } -#ifdef TLS_SUPPORT /* * If tls == 2, that means that the user requested "initial" TLS, * which happens right after the connection has opened. Do that @@ -273,21 +178,14 @@ smtp_init (char *client, char *server, char *port, int watch, int verbose, */ if (tls == 2) { - result = tls_negotiate(); - - /* - * Note: if tls_negotiate() fails it will call sm_end() for us, - * which closes the connection. - */ - if (result != RP_OK) - return result; + if (netsec_negotiate_tls(nsc, &errstr) != OK) { + sm_end(NOTOK); + return sm_nerror(errstr); + } } -#endif /* TLS_SUPPORT */ - sm_alarmed = 0; - alarm (SM_OPEN); + netsec_set_timeout(nsc, SM_OPEN); result = smhear (); - alarm (0); switch (result) { case 220: @@ -314,7 +212,6 @@ smtp_init (char *client, char *server, char *port, int watch, int verbose, return RP_RPLY; } -#ifdef TLS_SUPPORT /* * If the user requested TLS support, then try to do the STARTTLS command * as part of the initial dialog. Assuming this works, we then need to @@ -339,10 +236,10 @@ smtp_init (char *client, char *server, char *port, int watch, int verbose, * negotiation. Oblige them. */ - result = tls_negotiate(); - - if (result != RP_OK) - return result; + if (netsec_negotiate_tls(nsc, &errstr) != OK) { + sm_end(NOTOK); + return sm_nerror(errstr); + } doingEHLO = 1; result = smtalk (SM_HELO, "EHLO %s", client); @@ -353,11 +250,7 @@ smtp_init (char *client, char *server, char *port, int watch, int verbose, return RP_RPLY; } } -#else /* TLS_SUPPORT */ - NMH_UNUSED (tls); -#endif /* TLS_SUPPORT */ -#ifdef CYRUS_SASL /* * If the user asked for SASL, then check to see if the SMTP server * supports it. Otherwise, error out (because the SMTP server @@ -372,34 +265,9 @@ smtp_init (char *client, char *server, char *port, int watch, int verbose, return sm_ierror("SMTP server does not support SASL"); } - if (saslmech && stringdex(saslmech, server_mechs) == -1) { - sm_end(NOTOK); - return sm_ierror("Requested SASL mech \"%s\" is not in the " - "list of supported mechanisms:\n%s", - saslmech, server_mechs); - } - - /* Don't call sm_auth_sasl() for XAUTH2 with -sasl. Instead, call - sm_auth_xoauth2() below. */ - if (oauth_svc == NULL && - sm_auth_sasl(user, saslssf, saslmech ? saslmech : server_mechs, - server) != RP_OK) { - sm_end(NOTOK); - return NOTOK; - } - } -#endif /* CYRUS_SASL */ - - if (oauth_svc != NULL) { - char *server_mechs; - if ((server_mechs = EHLOset("AUTH")) == NULL - || stringdex("XOAUTH2", server_mechs) == -1) { - sm_end(NOTOK); - return sm_ierror("SMTP server does not support SASL XOAUTH2"); - } - if (sm_auth_xoauth2(user, oauth_svc, debug) != RP_OK) { + if (netsec_negotiate_sasl(nsc, server_mechs, &errstr) != OK) { sm_end(NOTOK); - return NOTOK; + return sm_nerror(errstr); } } @@ -411,28 +279,19 @@ send_options: ; } int -sendmail_init (char *client, char *server, int watch, int verbose, - int debug, int sasl, int saslssf, char *saslmech, char *user) +sendmail_init (char *client, int watch, int verbose, int debug, int sasl, + const char *saslmech, const char *user) { unsigned int i, result, vecp; int pdi[2], pdo[2]; - char *vec[15]; -#ifdef CYRUS_SASL - char *server_mechs; -#else /* CYRUS_SASL */ - NMH_UNUSED (server); - NMH_UNUSED (sasl); - NMH_UNUSED (saslssf); - NMH_UNUSED (saslmech); - NMH_UNUSED (user); -#endif /* CYRUS_SASL */ + char *vec[15], *errstr; if (watch) verbose = TRUE; sm_verbose = verbose; sm_debug = debug; - if (sm_rfp != NULL && sm_wfp != NULL) + if (nsc) return RP_OK; if (client == NULL || *client == '\0') { @@ -449,12 +308,19 @@ sendmail_init (char *client, char *server, int watch, int verbose, if (client == NULL || *client == '\0') client = "localhost"; -#if defined(CYRUS_SASL) || defined(TLS_SUPPORT) - sasl_inbuffer = malloc(SASL_MAXRECVBUF); - if (!sasl_inbuffer) - return sm_ierror("Unable to allocate %d bytes for read buffer", - SASL_MAXRECVBUF); -#endif /* CYRUS_SASL || TLS_SUPPORT */ + nsc = netsec_init(); + + if (user) + netsec_set_userid(nsc, user); + + if (sm_debug) + netsec_set_snoop(nsc, 1); + + if (sasl) { + if (netsec_set_sasl_params(nsc, client, "smtp", saslmech, + sm_sasl_callback, &errstr) != OK) + return sm_nerror(errstr); + } if (pipe (pdi) == NOTOK) return sm_ierror ("no pipes"); @@ -501,22 +367,14 @@ sendmail_init (char *client, char *server, int watch, int verbose, _exit (-1); /* NOTREACHED */ default: - SIGNAL (SIGALRM, alrmser); SIGNAL (SIGPIPE, SIG_IGN); close (pdi[1]); close (pdo[0]); - if ((sm_rfp = fdopen (pdi[0], "r")) == NULL - || (sm_wfp = fdopen (pdo[1], "w")) == NULL) { - close (pdi[0]); - close (pdo[1]); - sm_rfp = sm_wfp = NULL; - return sm_ierror ("unable to fdopen"); - } - sm_alarmed = 0; - alarm (SM_OPEN); + + netsec_set_fd(nsc, pdi[i], pdo[1]); + netsec_set_timeout(nsc, SM_OPEN); result = smhear (); - alarm (0); switch (result) { case 220: break; @@ -542,34 +400,24 @@ sendmail_init (char *client, char *server, int watch, int verbose, return RP_RPLY; } -#ifdef CYRUS_SASL - /* - * If the user asked for SASL, then check to see if the SMTP server - * supports it. Otherwise, error out (because the SMTP server - * might have been spoofed; we don't want to just silently not - * do authentication - */ - - if (sasl) { - if (! (server_mechs = EHLOset("AUTH"))) { - sm_end(NOTOK); - return sm_ierror("SMTP server does not support SASL"); - } - - if (saslmech && stringdex(saslmech, server_mechs) == -1) { - sm_end(NOTOK); - return sm_ierror("Requested SASL mech \"%s\" is not in the " - "list of supported mechanisms:\n%s", - saslmech, server_mechs); - } - - if (sm_auth_sasl(user, saslssf, saslmech ? saslmech : server_mechs, - server) != RP_OK) { - sm_end(NOTOK); - return NOTOK; - } - } -#endif /* CYRUS_SASL */ + /* + * If the user asked for SASL, then check to see if the SMTP server + * supports it. Otherwise, error out (because the SMTP server + * might have been spoofed; we don't want to just silently not + * do authentication + */ + + if (sasl) { + char *server_mechs; + if (! (server_mechs = EHLOset("AUTH"))) { + sm_end(NOTOK); + return sm_ierror("SMTP server does not support SASL"); + } + if (netsec_negotiate_sasl(nsc, server_mechs, &errstr) != OK) { + sm_end(NOTOK); + return sm_nerror(errstr); + } + } if (watch) smtalk (SM_HELO, "VERB on"); @@ -694,10 +542,7 @@ sm_wtxt (char *buffer, int len) { int result; - sm_alarmed = 0; - alarm (SM_TEXT); result = sm_wstream (buffer, len); - alarm (0); return (result == NOTOK ? RP_BHST : RP_OK); } @@ -746,7 +591,7 @@ sm_end (int type) } } - if (sm_rfp == NULL && sm_wfp == NULL) + if (nsc == NULL) return RP_OK; switch (type) { @@ -786,36 +631,13 @@ sm_end (int type) break; } -#ifdef TLS_SUPPORT - if (tls_active) { - BIO_ssl_shutdown(io); - BIO_free_all(io); - } -#endif /* TLS_SUPPORT */ - - if (sm_rfp != NULL) { - alarm (SM_CLOS); - fclose (sm_rfp); - alarm (0); - } - if (sm_wfp != NULL) { - alarm (SM_CLOS); - fclose (sm_wfp); - alarm (0); + if (nsc != NULL) { + netsec_shutdown(nsc, 1); + nsc = NULL; } if (sm_mts == MTS_SMTP) { status = 0; -#ifdef CYRUS_SASL - if (conn) { - sasl_dispose(&conn); - if (sasl_outbuffer) { - free(sasl_outbuffer); - } - } - if (sasl_inbuffer) - free(sasl_inbuffer); -#endif /* CYRUS_SASL */ } else if (sm_child != NOTOK) { status = pidwait (sm_child, OK); sm_child = NOTOK; @@ -823,407 +645,37 @@ sm_end (int type) status = OK; } - sm_rfp = sm_wfp = NULL; return (status ? RP_BHST : RP_OK); } -#ifdef CYRUS_SASL -/* - * This function implements SASL authentication for SMTP. If this function - * completes successfully, then authentication is successful and we've - * (optionally) negotiated a security layer. - */ - -#define CHECKB64SIZE(insize, outbuf, outsize) \ - { size_t wantout = (((insize + 2) / 3) * 4) + 32; \ - if (wantout > outsize) { \ - outbuf = mh_xrealloc(outbuf, outsize = wantout); \ - } \ - } static int -sm_auth_sasl(char *user, int saslssf, char *mechlist, char *inhost) +sm_ierror (const char *fmt, ...) { - int result, status; - unsigned int buflen, outlen; - char *buf, *outbuf = NULL, host[NI_MAXHOST]; - const char *chosen_mech; - sasl_security_properties_t secprops; - sasl_ssf_t *ssf; - int *outbufmax; - struct nmh_creds creds = { 0, 0, 0 }; - size_t outbufsize = 0; - - /* - * Initialize the callback contexts - */ - - /* - * This is a _bit_ of a hack ... but if the hostname wasn't supplied - * to us on the command line, then call getpeername and do a - * reverse-address lookup on the IP address to get the name. - */ - - memset(host, 0, sizeof(host)); - - if (!inhost) { - struct sockaddr_storage sin; - socklen_t len = sizeof(sin); - int result; - - if (getpeername(fileno(sm_wfp), (struct sockaddr *) &sin, &len) < 0) { - sm_ierror("getpeername on SMTP socket failed: %s", - strerror(errno)); - return NOTOK; - } - - result = getnameinfo((struct sockaddr *) &sin, len, host, sizeof(host), - NULL, 0, NI_NAMEREQD); - if (result != 0) { - sm_ierror("Unable to look up name of connected host: %s", - gai_strerror(result)); - return NOTOK; - } - } else { - strncpy(host, inhost, sizeof(host) - 1); - } - - /* It's OK to copy the addresses here. The callbacks that use - them will only be called before this function returns. */ - creds.host = host; - creds.user = user; - callbacks[SM_SASL_N_CB_USER].context = &creds; - callbacks[SM_SASL_N_CB_AUTHNAME].context = &creds; - callbacks[SM_SASL_N_CB_PASS].context = &creds; - - result = sasl_client_init(callbacks); - - if (result != SASL_OK) { - sm_ierror("SASL library initialization failed: %s", - sasl_errstring(result, NULL, NULL)); - return NOTOK; - } - - result = sasl_client_new("smtp", host, NULL, NULL, NULL, 0, &conn); - - if (result != SASL_OK) { - sm_ierror("SASL client initialization failed: %s", - sasl_errstring(result, NULL, NULL)); - return NOTOK; - } - - /* - * Initialize the security properties. But if TLS is active, then - * don't negotiate encryption here. - */ - - memset(&secprops, 0, sizeof(secprops)); - secprops.maxbufsize = SASL_MAXRECVBUF; - secprops.max_ssf = - tls_active ? 0 : (saslssf != -1 ? (unsigned int) saslssf : UINT_MAX); - - result = sasl_setprop(conn, SASL_SEC_PROPS, &secprops); - - if (result != SASL_OK) { - sm_ierror("SASL security property initialization failed: %s", - sasl_errstring(result, NULL, NULL)); - return NOTOK; - } - - /* - * Start the actual protocol. Feed the mech list into the library - * and get out a possible initial challenge - */ - - result = sasl_client_start(conn, mechlist, NULL, (const char **) &buf, - &buflen, (const char **) &chosen_mech); - - if (result != SASL_OK && result != SASL_CONTINUE) { - sm_ierror("SASL client start failed: %s", sasl_errdetail(conn)); - return NOTOK; - } - - /* - * If we got an initial challenge, send it as part of the AUTH - * command; otherwise, just send a plain AUTH command. - */ - - if (buflen) { - CHECKB64SIZE(buflen, outbuf, outbufsize); - status = sasl_encode64(buf, buflen, outbuf, outbufsize, NULL); - if (status != SASL_OK) { - sm_ierror("SASL base64 encode failed: %s", - sasl_errstring(status, NULL, NULL)); - if (outbuf) - free(outbuf); - return NOTOK; - } - - status = smtalk(SM_AUTH, "AUTH %s %s", chosen_mech, outbuf); - } else - status = smtalk(SM_AUTH, "AUTH %s", chosen_mech); - - /* - * Now we loop until we either fail, get a SASL_OK, or a 235 - * response code. Receive the challenges and process them until - * we're all done. - */ - - while (result == SASL_CONTINUE) { - - /* - * If we get a 235 response, that means authentication has - * succeeded and we need to break out of the loop (yes, even if - * we still get SASL_CONTINUE from sasl_client_step()). - * - * Otherwise, if we get a message that doesn't seem to be a - * valid response, then abort - */ - - if (status == 235) - break; - else if (status < 300 || status > 399) { - if (outbuf) - free(outbuf); - return RP_BHST; - } - - /* - * Special case; a zero-length response from the SMTP server - * is returned as a single =. If we get that, then set buflen - * to be zero. Otherwise, just decode the response. - */ - - if (strcmp("=", sm_reply.text) == 0) { - outlen = 0; - } else { - if (sm_reply.length > (int) outbufsize) { - outbuf = mh_xrealloc(outbuf, outbufsize = sm_reply.length); - } - - result = sasl_decode64(sm_reply.text, sm_reply.length, - outbuf, outbufsize, &outlen); - if (result != SASL_OK) { - smtalk(SM_AUTH, "*"); - sm_ierror("SASL base64 decode failed: %s", - sasl_errstring(result, NULL, NULL)); - if (outbuf) - free(outbuf); - return NOTOK; - } - } - - result = sasl_client_step(conn, outbuf, outlen, NULL, - (const char **) &buf, &buflen); - - if (result != SASL_OK && result != SASL_CONTINUE) { - smtalk(SM_AUTH, "*"); - sm_ierror("SASL client negotiation failed: %s", - sasl_errstring(result, NULL, NULL)); - if (outbuf) - free(outbuf); - return NOTOK; - } - - CHECKB64SIZE(buflen, outbuf, outbufsize); - status = sasl_encode64(buf, buflen, outbuf, outbufsize, NULL); - - if (status != SASL_OK) { - smtalk(SM_AUTH, "*"); - sm_ierror("SASL base64 encode failed: %s", - sasl_errstring(status, NULL, NULL)); - if (outbuf) - free(outbuf); - return NOTOK; - } - - status = smtalk(SM_AUTH, outbuf); - } - - if (outbuf) - free(outbuf); - - /* - * Make sure that we got the correct response - */ - - if (status < 200 || status > 299) - return RP_BHST; - - /* - * We _should_ have completed the authentication successfully. - * Get a few properties from the authentication exchange. - */ - - result = sasl_getprop(conn, SASL_MAXOUTBUF, (const void **) &outbufmax); - - if (result != SASL_OK) { - sm_ierror("Cannot retrieve SASL negotiated output buffer size: %s", - sasl_errstring(result, NULL, NULL)); - return NOTOK; - } - - maxoutbuf = *outbufmax; - - result = sasl_getprop(conn, SASL_SSF, (const void **) &ssf); - - sasl_ssf = *ssf; - - if (result != SASL_OK) { - sm_ierror("Cannot retrieve SASL negotiated security strength " - "factor: %s", sasl_errstring(result, NULL, NULL)); - return NOTOK; - } - - if (sasl_ssf > 0) { - sasl_outbuffer = malloc(maxoutbuf); + va_list ap; - if (sasl_outbuffer == NULL) { - sm_ierror("Unable to allocate %d bytes for SASL output " - "buffer", maxoutbuf); - return NOTOK; - } - sasl_outbuflen = 0; - sasl_inbuflen = 0; - sasl_inptr = sasl_inbuffer; - } else { - sasl_outbuffer = NULL; - /* Don't NULL out sasl_inbuffer because it could be used in - sm_fgetc (). */ - } + va_start(ap, fmt); + vsnprintf (sm_reply.text, sizeof(sm_reply.text), fmt, ap); + va_end(ap); - sasl_complete = 1; + sm_reply.length = strlen (sm_reply.text); + sm_reply.code = NOTOK; - return RP_OK; + return RP_BHST; } /* - * Our callback functions to feed data to the SASL library + * Like sm_ierror, but assume it's an allocated error string we need to free. */ static int -sm_get_user(void *context, int id, const char **result, unsigned *len) -{ - nmh_creds_t creds = (nmh_creds_t) context; - - if (! result || ((id != SASL_CB_USER) && (id != SASL_CB_AUTHNAME))) - return SASL_BADPARAM; - - if (creds->user == NULL) { - /* - * Pass the 1 third argument to nmh_get_credentials() so - * that a default user if the -user switch to send(1)/post(8) - * wasn't used, and so that a default password will be supplied. - * That's used when those values really don't matter, and only - * with legacy/.netrc, i.e., with a credentials profile entry. - */ - if (nmh_get_credentials (creds->host, creds->user, 1, creds) != OK) { - return SASL_BADPARAM; - } - } - - *result = creds->user; - if (len) - *len = strlen(creds->user); - - return SASL_OK; -} - -static int -sm_get_pass(sasl_conn_t *conn, void *context, int id, - sasl_secret_t **psecret) +sm_nerror (char *str) { - nmh_creds_t creds = (nmh_creds_t) context; - int len; - - NMH_UNUSED (conn); - - if (! psecret || id != SASL_CB_PASS) - return SASL_BADPARAM; - - if (creds->password == NULL) { - /* - * Pass the 0 third argument to nmh_get_credentials() so - * that the default password isn't used. With legacy/.netrc - * credentials support, we'll only get here if the -user - * switch to send(1)/post(8) wasn't used. - */ - if (nmh_get_credentials (creds->host, creds->user, 0, creds) != OK) { - return SASL_BADPARAM; - } - } - - len = strlen (creds->password); - - if (! (*psecret = (sasl_secret_t *) malloc(sizeof(sasl_secret_t) + len))) { - return SASL_NOMEM; - } - - (*psecret)->len = len; - strcpy((char *) (*psecret)->data, creds->password); - - return SASL_OK; -} -#endif /* CYRUS_SASL */ - -/* https://developers.google.com/gmail/xoauth2_protocol */ -static int -sm_auth_xoauth2(const char *user, const char *oauth_svc, int snoop) -{ - const char *xoauth_client_res; - int status; - -#ifdef OAUTH_SUPPORT - xoauth_client_res = mh_oauth_do_xoauth(user, oauth_svc, - snoop ? stderr : NULL); - - if (xoauth_client_res == NULL) { - return sm_ierror("Internal error: mh_oauth_do_xoauth() returned NULL"); - } -#else - NMH_UNUSED(user); - NMH_UNUSED(snoop); - adios(NULL, "sendfrom built without OAUTH_SUPPORT, " - "so oauth_svc %s is not supported", oauth_svc); -#endif /* OAUTH_SUPPORT */ - - status = smtalk(SM_AUTH, "AUTH XOAUTH2 %s", xoauth_client_res); - if (status == 235) { - /* It worked! */ - return RP_OK; - } - - /* - * Status is 334 and sm_reply.text contains base64-encoded JSON. As far as - * epg can tell, no matter the error, the JSON is always the same: - * {"status":"400","schemes":"Bearer","scope":"https://mail.google.com/"} - * I tried these errors: - * - garbage token - * - expired token - * - wrong scope - * - wrong username - */ - /* Then we're supposed to send an empty response ("\r\n"). */ - smtalk(SM_AUTH, ""); - /* - * And now we always get this, again, no matter the error: - * 535-5.7.8 Username and Password not accepted. Learn more at - * 535 5.7.8 http://support.google.com/mail/bin/answer.py?answer=14257 - */ - return RP_BHST; -} - -static int -sm_ierror (char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - vsnprintf (sm_reply.text, sizeof(sm_reply.text), fmt, ap); - va_end(ap); - - sm_reply.length = strlen (sm_reply.text); + strncpy(sm_reply.text, str, sizeof(sm_reply.text)); + sm_reply.text[sizeof(sm_reply.text) - 1] = '\0'; + sm_reply.length = strlen(sm_reply.text); sm_reply.code = NOTOK; + free(str); return RP_BHST; } @@ -1232,334 +684,91 @@ static int smtalk (int time, char *fmt, ...) { va_list ap; + char *errstr; int result; - char *buffer; - size_t bufsize = BUFSIZ; - - buffer = mh_xmalloc(bufsize); va_start(ap, fmt); - result = vsnprintf (buffer, bufsize, fmt, ap); + result = netsec_vprintf (nsc, &errstr, fmt, ap); va_end(ap); - if (result > (int) bufsize) { - buffer = mh_xrealloc(buffer, bufsize = result + 1); - va_start(ap, fmt); - vsnprintf (buffer, bufsize, fmt, ap); - va_end(ap); - } - - if (sm_debug) { - char *decoded_buffer = - prepare_for_display (buffer, &next_line_encoded); - - if (sasl_ssf) - printf("(sasl-encrypted) "); - if (tls_active) - printf("(tls-encrypted) "); - printf ("=> %s\n", decoded_buffer); - free (decoded_buffer); - fflush (stdout); - } - - sm_alarmed = 0; - alarm ((unsigned) time); - if ((result = sm_wrecord (buffer, strlen (buffer))) != NOTOK) - result = smhear (); - alarm (0); - - free(buffer); + if (result != OK) + return sm_nerror(errstr); - return result; -} + if (netsec_printf (nsc, &errstr, "\r\n") != OK) + return sm_nerror(errstr); + if (netsec_flush (nsc, &errstr) != OK) + return sm_nerror(errstr); -/* - * write the buffer to the open SMTP channel - */ + netsec_set_timeout(nsc, time); -static int -sm_wrecord (char *buffer, int len) -{ - if (sm_wfp == NULL) - return sm_werror (); - - sm_fwrite (buffer, len); - sm_fputs ("\r\n"); - sm_fflush (); - - return (ferror (sm_wfp) ? sm_werror () : OK); + return smhear (); } static int sm_wstream (char *buffer, int len) { - char *bp; + char *bp, *errstr; static char lc = '\0'; + int rc; - if (sm_wfp == NULL) - return sm_werror (); + if (nsc == NULL) { + sm_ierror("No socket opened"); + return NOTOK; + } if (buffer == NULL && len == 0) { + rc = OK; if (lc != '\n') - sm_fputs ("\r\n"); + rc = netsec_write(nsc, "\r\n", 2, &errstr); lc = '\0'; - return (ferror (sm_wfp) ? sm_werror () : OK); + if (rc != OK) + sm_nerror(errstr); + return rc; } for (bp = buffer; bp && len > 0; bp++, len--) { switch (*bp) { case '\n': sm_nl = TRUE; - sm_fputc ('\r'); + if (netsec_write(nsc, "\r", 1, &errstr) != OK) { + sm_nerror(errstr); + return NOTOK; + } break; case '.': if (sm_nl) - sm_fputc ('.');/* FALL THROUGH */ + if (netsec_write(nsc, ".", 1, &errstr) != OK) { + sm_nerror(errstr); + return NOTOK; + } /* FALL THROUGH */ + default: sm_nl = FALSE; } - sm_fputc (*bp); - if (ferror (sm_wfp)) - return sm_werror (); + if (netsec_write(nsc, bp, 1, &errstr) != OK) { + sm_nerror(errstr); + return NOTOK; + } } if (bp > buffer) lc = *--bp; - return (ferror (sm_wfp) ? sm_werror () : OK); -} - -/* - * Write out to the network, but do buffering for SASL (if enabled) - */ - -static int -sm_fwrite(char *buffer, int len) -{ -#ifdef CYRUS_SASL - const char *output; - unsigned int outputlen; - - if (sasl_complete == 0 || sasl_ssf == 0) { -#endif /* CYRUS_SASL */ -#ifdef TLS_SUPPORT - if (tls_active) { - int ret; - - ret = BIO_write(io, buffer, len); - - if (ret <= 0) { - sm_ierror("TLS error during write: %s", - ERR_error_string(ERR_get_error(), NULL)); - return NOTOK; - } - } else -#endif /* TLS_SUPPORT */ - if ((int) fwrite(buffer, sizeof(*buffer), len, sm_wfp) < len) { - advise ("sm_fwrite", "fwrite"); - } -#ifdef CYRUS_SASL - } else { - while (len >= maxoutbuf - sasl_outbuflen) { - memcpy(sasl_outbuffer + sasl_outbuflen, buffer, - maxoutbuf - sasl_outbuflen); - len -= maxoutbuf - sasl_outbuflen; - sasl_outbuflen = 0; - - if (sasl_encode(conn, sasl_outbuffer, maxoutbuf, - &output, &outputlen) != SASL_OK) { - sm_ierror("Unable to SASL encode connection data: %s", - sasl_errdetail(conn)); - return NOTOK; - } - - if (fwrite(output, sizeof(*output), outputlen, sm_wfp) < - outputlen) { - advise ("sm_fwrite", "fwrite"); - } - } - - if (len > 0) { - memcpy(sasl_outbuffer + sasl_outbuflen, buffer, len); - sasl_outbuflen += len; - } - } -#endif /* CYRUS_SASL */ - return ferror(sm_wfp) ? NOTOK : RP_OK; -} - -#ifdef TLS_SUPPORT -/* - * Negotiate Transport Layer Security - */ - -static int -tls_negotiate(void) -{ - BIO *ssl_bio; - - if (! sslctx) { - const SSL_METHOD *method; - - SSL_library_init(); - SSL_load_error_strings(); - - method = TLSv1_client_method(); /* Not sure about this */ - - /* Older ssl takes a non-const arg. */ - sslctx = SSL_CTX_new((SSL_METHOD *) method); - - if (! sslctx) { - sm_end(NOTOK); - return sm_ierror("Unable to initialize OpenSSL context: %s", - ERR_error_string(ERR_get_error(), NULL)); - } - } - - ssl = SSL_new(sslctx); - - if (! ssl) { - sm_end(NOTOK); - return sm_ierror("Unable to create SSL connection: %s", - ERR_error_string(ERR_get_error(), NULL)); - } - - sbior = BIO_new_socket(fileno(sm_rfp), BIO_NOCLOSE); - sbiow = BIO_new_socket(fileno(sm_wfp), BIO_NOCLOSE); - - if (sbior == NULL || sbiow == NULL) { - sm_end(NOTOK); - return sm_ierror("Unable to create BIO endpoints: %s", - ERR_error_string(ERR_get_error(), NULL)); - } - - SSL_set_bio(ssl, sbior, sbiow); - SSL_set_connect_state(ssl); - - /* - * Set up a BIO to handle buffering for us - */ - - io = BIO_new(BIO_f_buffer()); - - if (! io) { - sm_end(NOTOK); - return sm_ierror("Unable to create a buffer BIO: %s", - ERR_error_string(ERR_get_error(), NULL)); - } - - ssl_bio = BIO_new(BIO_f_ssl()); - - if (! ssl_bio) { - sm_end(NOTOK); - return sm_ierror("Unable to create a SSL BIO: %s", - ERR_error_string(ERR_get_error(), NULL)); - } - - BIO_set_ssl(ssl_bio, ssl, BIO_CLOSE); - BIO_push(io, ssl_bio); - - /* - * Try doing the handshake now - */ - - if (BIO_do_handshake(io) < 1) { - sm_end(NOTOK); - return sm_ierror("Unable to negotiate SSL connection: %s", - ERR_error_string(ERR_get_error(), NULL)); - } - - if (sm_debug) { - const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl); - printf("SSL negotiation successful: %s(%d) %s\n", - SSL_CIPHER_get_name(cipher), - SSL_CIPHER_get_bits(cipher, NULL), - SSL_CIPHER_get_version(cipher)); - - } - - tls_active = 1; - - return RP_OK; -} -#endif /* TLS_SUPPORT */ - -/* - * Convenience functions to replace occurrences of fputs() and fputc() - */ - -static int -sm_fputs(char *buffer) -{ - return sm_fwrite(buffer, strlen(buffer)); -} - -static int -sm_fputc(int c) -{ - char h = c; - - return sm_fwrite(&h, 1); -} - -/* - * Flush out any pending data on the connection - */ - -static void -sm_fflush(void) -{ -#ifdef CYRUS_SASL - const char *output; - unsigned int outputlen; - int result; - - if (sasl_complete == 1 && sasl_ssf > 0 && sasl_outbuflen > 0) { - result = sasl_encode(conn, sasl_outbuffer, sasl_outbuflen, - &output, &outputlen); - if (result != SASL_OK) { - sm_ierror("Unable to SASL encode connection data: %s", - sasl_errdetail(conn)); - return; - } - - if (fwrite(output, sizeof(*output), outputlen, sm_wfp) < outputlen) { - advise ("sm_fflush", "fwrite"); - } - sasl_outbuflen = 0; - } -#endif /* CYRUS_SASL */ - -#ifdef TLS_SUPPORT - if (tls_active) { - (void) BIO_flush(io); - } -#endif /* TLS_SUPPORT */ - - fflush(sm_wfp); -} - -static int -sm_werror (void) -{ - sm_reply.length = - strlen (strcpy (sm_reply.text, sm_wfp == NULL ? "no socket opened" - : sm_alarmed ? "write to socket timed out" - : "error writing to socket")); - - return (sm_reply.code = NOTOK); + return OK; } static int smhear (void) { - int i, code, cont, bc = 0, rc, more; + int i, code, cont, more; + size_t buflen, rc; unsigned char *bp; char *rp; - char **ehlo = EHLOkeys, buffer[BUFSIZ]; + char *errstr; + char **ehlo = EHLOkeys, *buffer; if (doingEHLO) { static int at_least_once = 0; @@ -1586,20 +795,8 @@ again: ; rp = sm_reply.text; rc = sizeof(sm_reply.text) - 1; - for (more = FALSE; sm_rrecord ((char *) (bp = (unsigned char *) buffer), - &bc) != NOTOK ; ) { - if (sm_debug) { - char *decoded_buffer = - prepare_for_display (buffer, &next_line_encoded); - - if (sasl_ssf > 0) - printf("(sasl-decrypted) "); - if (tls_active) - printf("(tls-decrypted) "); - printf ("<= %s\n", decoded_buffer); - free (decoded_buffer); - fflush (stdout); - } + for (more = FALSE; (buffer = netsec_readline(nsc, &buflen, + &errstr)) != NULL ; ) { if (doingEHLO && strncmp (buffer, "250", sizeof("250") - 1) == 0 @@ -1619,18 +816,20 @@ again: ; doingEHLO = 2; } - for (; bc > 0 && (!isascii (*bp) || !isdigit (*bp)); bp++, bc--) + bp = (unsigned char *) buffer; + + for (; buflen > 0 && (!isascii (*bp) || !isdigit (*bp)); bp++, buflen--) continue; cont = FALSE; code = atoi ((char *) bp); - bp += 3, bc -= 3; - for (; bc > 0 && isspace (*bp); bp++, bc--) + bp += 3, buflen -= 3; + for (; buflen > 0 && isspace (*bp); bp++, buflen--) continue; - if (bc > 0 && *bp == '-') { + if (buflen > 0 && *bp == '-') { cont = TRUE; - bp++, bc--; - for (; bc > 0 && isspace (*bp); bp++, bc--) + bp++, buflen--; + for (; buflen > 0 && isspace (*bp); bp++, buflen--) continue; } @@ -1641,20 +840,18 @@ again: ; } else { sm_reply.code = code; more = cont; - if (bc <= 0) { - /* can never fail to 0-terminate because of size of buffer vs fixed string */ - strncpy (buffer, sm_noreply, sizeof(buffer)); - bp = (unsigned char *) buffer; - bc = strlen (sm_noreply); + if (buflen <= 0) { + bp = (unsigned char *) sm_noreply; + buflen = strlen (sm_noreply); } } - if ((i = min (bc, rc)) > 0) { + if ((i = min (buflen, rc)) > 0) { memcpy (rp, bp, i); rp += i; rc -= i; i = strlen(sm_moreply); - if (more && rc > i + 1) { + if (more && (int) rc > i + 1) { memcpy (rp, sm_moreply, i); /* safe because of check in if() */ rp += i; rc -= i; @@ -1674,198 +871,9 @@ again: ; sm_reply.text[sm_reply.length] = 0; return sm_reply.code; } - return NOTOK; -} - - -static int -sm_rrecord (char *buffer, int *len) -{ - int retval; - - if (sm_rfp == NULL) - return sm_rerror(0); - - buffer[*len = 0] = 0; - - if ((retval = sm_fgets (buffer, BUFSIZ, sm_rfp)) != RP_OK) - return sm_rerror (retval); - *len = strlen (buffer); - /* *len should be >0 except on EOF, but check for safety's sake */ - if (*len == 0) - return sm_rerror (RP_EOF); - if (buffer[*len - 1] != '\n') - while ((retval = sm_fgetc (sm_rfp)) != '\n' && retval != EOF && - retval != -2) - continue; - else - if ((*len > 1) && (buffer[*len - 2] == '\r')) - *len -= 1; - *len -= 1; - buffer[*len] = 0; - - return OK; -} - -/* - * Our version of fgets, which calls our private fgetc function - */ - -static int -sm_fgets(char *buffer, int size, FILE *f) -{ - int c; - - do { - c = sm_fgetc(f); - - if (c == EOF) - return RP_EOF; - - if (c == -2) - return NOTOK; - *buffer++ = c; - } while (size > 1 && c != '\n'); - - *buffer = '\0'; - - return RP_OK; -} - - -#if defined(CYRUS_SASL) || defined(TLS_SUPPORT) -/* - * Read from the network, but do SASL or TLS encryption - */ - -static int -sm_fgetc(FILE *f) -{ - char tmpbuf[BUFSIZ], *retbuf; - unsigned int retbufsize = 0; - int cc, result; - - /* - * If we have leftover data, return it - */ - - if (sasl_inbuflen) { - sasl_inbuflen--; - return (int) *sasl_inptr++; - } - - /* - * If not, read from the network until we have some data to return - */ - - while (retbufsize == 0) { - -#ifdef TLS_SUPPORT - if (tls_active) { - cc = SSL_read(ssl, tmpbuf, sizeof(tmpbuf)); - - if (cc == 0) { - result = SSL_get_error(ssl, cc); - - if (result != SSL_ERROR_ZERO_RETURN) { - sm_ierror("TLS peer aborted connection"); - } - - return EOF; - } - - if (cc < 0) { - sm_ierror("SSL_read failed: %s", - ERR_error_string(ERR_get_error(), NULL)); - return -2; - } - } else -#endif /* TLS_SUPPORT */ - - cc = read(fileno(f), tmpbuf, sizeof(tmpbuf)); - - if (cc == 0) - return EOF; - - if (cc < 0) { - sm_ierror("Unable to read from network: %s", strerror(errno)); - return -2; - } - - /* - * Don't call sasl_decode unless sasl is complete and we have - * encryption working - */ - -#ifdef CYRUS_SASL - if (sasl_complete == 0 || sasl_ssf == 0) { - retbuf = tmpbuf; - retbufsize = cc; - } else { - result = sasl_decode(conn, tmpbuf, cc, (const char **) &retbuf, - &retbufsize); - - if (result != SASL_OK) { - sm_ierror("Unable to decode SASL network data: %s", - sasl_errdetail(conn)); - return -2; - } - } -#else /* ! CYRUS_SASL */ - retbuf = tmpbuf; - retbufsize = cc; -#endif /* CYRUS_SASL */ - } - - if (retbufsize > SASL_MAXRECVBUF) { - sm_ierror("Received data (%d bytes) is larger than the buffer " - "size (%d bytes)", retbufsize, SASL_MAXRECVBUF); - return -2; - } - - memcpy(sasl_inbuffer, retbuf, retbufsize); - sasl_inptr = sasl_inbuffer + 1; - sasl_inbuflen = retbufsize - 1; - - return (int) sasl_inbuffer[0]; -} -#endif /* CYRUS_SASL || TLS_SUPPORT */ - -static int -sm_rerror (int rc) -{ - if (sm_mts == MTS_SMTP) - sm_reply.length = - strlen (strcpy (sm_reply.text, sm_rfp == NULL ? "no socket opened" - : sm_alarmed ? "read from socket timed out" - : rc == RP_EOF ? "premature end-of-file on socket" - : "error reading from socket")); - else - sm_reply.length = - strlen (strcpy (sm_reply.text, sm_rfp == NULL ? "no pipe opened" - : sm_alarmed ? "read from pipe timed out" - : rc == RP_EOF ? "premature end-of-file on pipe" - : "error reading from pipe")); - - return (sm_reply.code = NOTOK); -} - - -static void -alrmser (int i) -{ - NMH_UNUSED (i); - -#ifndef RELIABLE_SIGNALS - SIGNAL (SIGALRM, alrmser); -#endif - - sm_alarmed++; - if (sm_debug) { - printf ("timed out...\n"); - fflush (stdout); - } + sm_nerror(errstr); + return NOTOK; } @@ -1940,68 +948,143 @@ EHLOset (char *s) return 0; } - /* - * Detects, using heuristics, if an SMTP server or client response string - * contains a base64-encoded portion. If it does, decodes it and replaces - * any non-printable characters with a hex representation. Caller is - * responsible for free'ing return value. If the decode fails, a copy of - * the input string is returned. + * Our SASL callback; we are either given SASL tokens to generate network + * protocols messages for, or we decode incoming protocol messages and + * convert them to binary SASL tokens to pass up into the SASL library. */ -static -char * -prepare_for_display (const char *string, int *next_line_encoded) { - const char *start = NULL; - const char *decoded; - size_t decoded_len; - int prefix_len = -1; - - if (strncmp (string, "AUTH ", 5) == 0) { - /* AUTH line: the mechanism isn't encoded. If there's an initial - response, it must be base64 encoded.. */ - char *mechanism = strchr (string + 5, ' '); - - if (mechanism != NULL) { - prefix_len = (int) (mechanism - string + 1); - } /* else no space following the mechanism, so no initial response */ - *next_line_encoded = 0; - } else if (strncmp (string, "334 ", 4) == 0) { - /* 334 is the server's request for user or password. */ - prefix_len = 4; - /* The next (client response) line must be base64 encoded. */ - *next_line_encoded = 1; - } else if (*next_line_encoded) { - /* "next" line now refers to this line, which is a base64-encoded - client response. */ - prefix_len = 0; - *next_line_encoded = 0; - } else { - *next_line_encoded = 0; - } - /* Don't attempt to decoded unencoded initial response ('=') or cancel - response ('*'). */ - if (prefix_len > -1 && - string[prefix_len] != '=' && string[prefix_len] != '*') { - start = string + prefix_len; - } +static int +sm_sasl_callback(enum sasl_message_type mtype, unsigned const char *indata, + unsigned int indatalen, unsigned char **outdata, + unsigned int *outdatalen, char **errstr) +{ + int rc, snoopoffset; + char *mech, *line; + size_t len; - if (start && decodeBase64 (start, &decoded, &decoded_len, 1, NULL) == OK) { - char *hexified; - char *prefix = mh_xmalloc(prefix_len + 1); - char *display_string; - - /* prefix is the beginning portion, which isn't base64 encoded. */ - snprintf (prefix, prefix_len + 1, "%*s", prefix_len, string); - hexify ((const unsigned char *) decoded, decoded_len, &hexified); - /* Wrap the decoded portion in "b64<>". */ - display_string = concat (prefix, "b64<", hexified, ">", NULL); - free (hexified); - free (prefix); - free ((char *) decoded); - - return display_string; - } else { - return getcpy (string); + switch (mtype) { + case NETSEC_SASL_START: + /* + * Generate an AUTH message; if we were given an input token + * then generate a an AUTH message that includes the initial + * response. + */ + + mech = netsec_get_sasl_mechanism(nsc); + + if (indatalen) { + char *b64data; + + b64data = mh_xmalloc(BASE64SIZE(indatalen)); + writeBase64raw(indata, indatalen, (unsigned char *) b64data); + + netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, + &snoopoffset); + snoopoffset = 6 + strlen(mech); + rc = netsec_printf(nsc, errstr, "AUTH %s %s\r\n", mech, b64data); + free(b64data); + netsec_set_snoop_callback(nsc, NULL, NULL); + } else { + rc = netsec_printf(nsc, errstr, "AUTH %s\r\n", mech); + } + + if (rc != OK) + return NOTOK; + + if (netsec_flush(nsc, errstr) != OK) + return NOTOK; + + break; + + case NETSEC_SASL_READ: + /* + * Read in a line that should contain a 334 response code, followed + * by base64 response data. + */ + + netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, &snoopoffset); + snoopoffset = 4; + line = netsec_readline(nsc, &len, errstr); + netsec_set_snoop_callback(nsc, NULL, NULL); + + if (line == NULL) + return NOTOK; + + if (len < 4) { + netsec_err(errstr, "Invalid format for SASL response"); + return NOTOK; + } + + if (strncmp(line, "334 ", 4) != 0) { + netsec_err(errstr, "Improper SASL protocol response: %s", line); + return NOTOK; + } + + if (len == 4) { + *outdata = NULL; + *outdatalen = 0; + } else { + rc = decodeBase64(line + 4, outdata, &len, 0, NULL); + if (rc != OK) { + netsec_err(errstr, "Unable to decode base64 response"); + return NOTOK; + } + *outdatalen = len; + } + break; + + case NETSEC_SASL_WRITE: + /* + * The output encoding is pretty simple, so this is easy. + */ + if (indatalen == 0) { + rc = netsec_printf(nsc, errstr, "\r\n"); + } else { + unsigned char *b64data; + b64data = mh_xmalloc(BASE64SIZE(indatalen)); + writeBase64raw(indata, indatalen, b64data); + netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, NULL); + rc = netsec_printf(nsc, errstr, "%s\r\n", b64data); + netsec_set_snoop_callback(nsc, NULL, NULL); + free(b64data); + } + + if (rc != OK) + return NOTOK; + + if (netsec_flush(nsc, errstr) != OK) + return NOTOK; + break; + + case NETSEC_SASL_FINISH: + /* + * Finish the protocol; we're looking for a 235 message. + */ + line = netsec_readline(nsc, &len, errstr); + if (line == NULL) + return NOTOK; + + if (strncmp(line, "235 ", 4) != 0) { + if (len > 4) + netsec_err(errstr, "Authentication failed: %s", line + 4); + else + netsec_err(errstr, "Authentication failed: %s", line); + return NOTOK; + } + break; + + case NETSEC_SASL_CANCEL: + /* + * Cancel the SASL exchange; this is done by sending a single "*". + */ + rc = netsec_printf(nsc, errstr, "*\r\n"); + if (rc == OK) + rc = netsec_flush(nsc, errstr); + if (rc != OK) + return NOTOK; + break; } + + return OK; } diff --git a/mts/smtp/smtp.h b/mts/smtp/smtp.h index 3063866e..fb842fe6 100644 --- a/mts/smtp/smtp.h +++ b/mts/smtp/smtp.h @@ -16,8 +16,8 @@ struct smtp { * prototypes */ /* int client (); */ -int sm_init (char *, char *, char *, int, int, int, int, int, char *, char *, - const char *, int); +int sm_init (char *, char *, char *, int, int, int, int, const char *, + const char *, const char *, int); int sm_winit (char *, int); int sm_wadr (char *, char *, char *); int sm_waend (void); diff --git a/sbr/base64.c b/sbr/base64.c index eb3c8ea2..79d7aa94 100644 --- a/sbr/base64.c +++ b/sbr/base64.c @@ -113,7 +113,7 @@ writeBase64aux (FILE *in, FILE *out, int crlf) 4 * [length/3] + length/57 + 2 But double the length will certainly be sufficient. */ int -writeBase64 (unsigned char *in, size_t length, unsigned char *out) +writeBase64 (const unsigned char *in, size_t length, unsigned char *out) { unsigned int n = BPERLIN; @@ -121,7 +121,7 @@ writeBase64 (unsigned char *in, size_t length, unsigned char *out) unsigned long bits; unsigned char *bp; unsigned int cc; - for (cc = 0, bp = in; length > 0 && cc < 3; ++cc, ++bp, --length) + for (cc = 0; length > 0 && cc < 3; ++cc, --length) /* empty */ ; if (cc == 0) { @@ -168,13 +168,13 @@ writeBase64 (unsigned char *in, size_t length, unsigned char *out) */ int -writeBase64raw (unsigned char *in, size_t length, unsigned char *out) +writeBase64raw (const unsigned char *in, size_t length, unsigned char *out) { while (1) { unsigned long bits; unsigned char *bp; unsigned int cc; - for (cc = 0, bp = in; length > 0 && cc < 3; ++cc, ++bp, --length) + for (cc = 0; length > 0 && cc < 3; ++cc, --length) /* empty */ ; if (cc == 0) { @@ -233,8 +233,8 @@ static unsigned char b642nib[0x80] = { * See description of arguments with declaration in h/prototypes.h. */ int -decodeBase64 (const char *encoded, const char **decoded, size_t *len, int skip_crs, - unsigned char *digest) { +decodeBase64 (const char *encoded, unsigned char **decoded, size_t *len, + int skip_crs, unsigned char *digest) { const char *cp = encoded; int self_delimiting = 0; int bitno, skip; @@ -314,7 +314,7 @@ test_end: return NOTOK; } - *decoded = charstring_buffer_copy (decoded_c); + *decoded = (unsigned char *) charstring_buffer_copy (decoded_c); *len = charstring_bytes (decoded_c); charstring_free (decoded_c); diff --git a/sbr/netsec.c b/sbr/netsec.c new file mode 100644 index 00000000..97d306b4 --- /dev/null +++ b/sbr/netsec.c @@ -0,0 +1,1605 @@ + +/* + * netsec.c -- Network security routines for handling protocols that + * require SASL and/or TLS. + * + * This code is Copyright (c) 2016, by the authors of nmh. See the + * COPYRIGHT file in the root directory of the nmh distribution for + * complete copyright information. + */ + +#include +#include +#include +#include +#include +#include + +#ifdef CYRUS_SASL +#include +#include +# 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 +#include + +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; +} diff --git a/sbr/oauth.c b/sbr/oauth.c index 3433fba0..f4564d0f 100755 --- a/sbr/oauth.c +++ b/sbr/oauth.c @@ -127,17 +127,16 @@ static boolean get_json_strings(const char *, size_t, FILE *, ...); static boolean make_query_url(char *, size_t, CURL *, const char *, ...); static boolean post(struct curl_ctx *, const char *, const char *); -char * -mh_oauth_do_xoauth(const char *user, const char *svc, FILE *log) +int +mh_oauth_do_xoauth(const char *user, const char *svc, unsigned char **oauth_res, + size_t *oauth_res_len, FILE *log) { mh_oauth_ctx *ctx; mh_oauth_cred *cred; char *fn; int failed_to_lock = 0; FILE *fp; - size_t client_res_len; char *client_res; - char *client_res_b64; if (!mh_oauth_new (&ctx, svc)) adios(NULL, mh_oauth_get_err_string(ctx)); @@ -183,18 +182,14 @@ mh_oauth_do_xoauth(const char *user, const char *svc, FILE *log) free(fn); /* XXX writeBase64raw modifies the source buffer! make a copy */ - client_res = getcpy(mh_oauth_sasl_client_response(&client_res_len, user, + client_res = getcpy(mh_oauth_sasl_client_response(oauth_res_len, user, cred)); mh_oauth_cred_free(cred); mh_oauth_free(ctx); - client_res_b64 = mh_xmalloc(((((client_res_len) + 2) / 3 ) * 4) + 1); - if (writeBase64raw((unsigned char *)client_res, client_res_len, - (unsigned char *)client_res_b64) != OK) { - adios(NULL, "base64 encoding of XOAUTH2 client response failed"); - } - free(client_res); - return client_res_b64; + *oauth_res = (unsigned char *) client_res; + + return OK; } static boolean diff --git a/test/common.sh.in b/test/common.sh.in index b2c7a7d7..4843a240 100644 --- a/test/common.sh.in +++ b/test/common.sh.in @@ -44,7 +44,7 @@ HOME=$MH_TEST_DIR export HOME unset MAILDROP MHBUILD MHCONTEXT MHMTSUSERCONF MHN MHSHOW MHSTORE -unset MHLDEBUG MHPDEBUG MHWDEBUG PAGER +unset MHLDEBUG MHPDEBUG MHWDEBUG PAGER XOAUTH #### Set LC_ALL in individual tests as needed. Unset these so #### that we don't depend on user's settings in other tests. unset LANG LC_ALL LC_CTYPE diff --git a/test/oauth/common.sh b/test/oauth/common.sh index ab0bad53..d2ac2a27 100644 --- a/test/oauth/common.sh +++ b/test/oauth/common.sh @@ -131,7 +131,7 @@ expect_creds() { } test_inc() { - run_test "inc -host 127.0.0.1 -port ${pop_port} -saslmech xoauth2 -authservice test -user nobody@example.com -width 80" "$@" + run_test "inc -host 127.0.0.1 -port ${pop_port} -sasl -saslmech xoauth2 -authservice test -user nobody@example.com -width 80" "$@" } test_inc_success() { @@ -142,7 +142,7 @@ test_inc_success() { } test_send_no_servers() { - run_test "send -draft -server 127.0.0.1 -port ${smtp_port} -saslmech xoauth2 -authservice test -user nobody@example.com" "$@" + run_test "send -draft -server 127.0.0.1 -port ${smtp_port} -sasl -saslmech xoauth2 -authservice test -user nobody@example.com" "$@" } test_send_only_fakesmtp() { diff --git a/test/oauth/test-inc b/test/oauth/test-inc index c7456928..96a2af04 100755 --- a/test/oauth/test-inc +++ b/test/oauth/test-inc @@ -77,6 +77,7 @@ Content-Type: application/json EOF start_fakehttp +start_pop_xoauth test_inc 'inc: error refreshing OAuth2 token inc: bad OAuth request; re-run with -snoop and send REDACTED output to nmh-workers' @@ -93,7 +94,7 @@ EOF start_pop_xoauth -test_inc 'inc: -ERR [AUTH] Invalid credentials.' +test_inc 'inc: Authentication failed: -ERR [AUTH] Invalid credentials.' # TEST start_test "pop server doesn't support oauth" diff --git a/test/oauth/test-send b/test/oauth/test-send index 98efb2b5..dc10080a 100755 --- a/test/oauth/test-send +++ b/test/oauth/test-send @@ -35,7 +35,7 @@ expire-nobody@example.com: 2000000000 EOF start_fakesmtp -run_test "send -draft -server 127.0.0.1 -port ${smtp_port} -saslmech xoauth2 -authservice test -user nobody@example.com" +run_test "send -draft -server 127.0.0.1 -port ${smtp_port} -sasl -saslmech xoauth2 -authservice test -user nobody@example.com" # TEST start_test 'expired access token, refresh works, smtp server accepts message' @@ -163,7 +163,7 @@ setup_draft # TEST start_test 'no service definition' -run_test "send -draft -server 127.0.0.1 -port ${smtp_port} -saslmech xoauth2 -authservice bogus -user nobody@example.com" 'send: Unable to retrieve oauth profile entries: scope is missing' +run_test "send -draft -server 127.0.0.1 -port ${smtp_port} -sasl -saslmech xoauth2 -authservice bogus -user nobody@example.com" 'send: Unable to retrieve oauth profile entries: scope is missing' # TEST start_test 'no creds file -- should tell user to mhlogin' @@ -269,7 +269,7 @@ EOF start_fakesmtp run_prog send -draft -server 127.0.0.1 -port ${smtp_port} \ - -saslmech xoauth2 -authservice test -user nobody@example.com > "${testname}.send-output" 2>&1 || true + -sasl -saslmech xoauth2 -authservice test -user nobody@example.com > "${testname}.send-output" 2>&1 || true # Clear out an error message we get from libcurl on some systems (seen at least # 3 different versions of this error message, on FreeBSD 10.1, Ubuntu 12.04, and # Ubuntu 14.04). @@ -358,7 +358,7 @@ access-nobody@example.com: test-access expire-nobody@example.com: 2000000000 EOF -test_send_only_fakesmtp 'post: problem initializing server; [BHST] Not no way, not no how! +test_send_only_fakesmtp 'post: problem initializing server; [BHST] Authentication failed: Not no way, not no how! send: message not delivered to anyone' # TEST @@ -385,7 +385,7 @@ start_test "smtp server doesn't support oauth" unset XOAUTH -test_send_only_fakesmtp 'post: problem initializing server; [BHST] SMTP server does not support SASL XOAUTH2 +test_send_only_fakesmtp 'post: problem initializing server; [BHST] SMTP server does not support SASL send: message not delivered to anyone' clean_fakesmtp diff --git a/test/oauth/test-sendfrom b/test/oauth/test-sendfrom index 4ed020b0..3c68a0fa 100755 --- a/test/oauth/test-sendfrom +++ b/test/oauth/test-sendfrom @@ -30,7 +30,7 @@ EOF sendfrom="$MH_INST_DIR${datarootdir}/doc/nmh/contrib/sendfrom" cat >> "$MH" < "${testname}.expected" < "${testname}.post-expected" < EHLO nosuchhost.example.com <= 250-ready @@ -76,7 +78,7 @@ cat > "${testname}.post-expected" < ${testname}.post 2>/dev/null + 2> ${testname}.post check "${testname}.post-expected" "${testname}.post" diff --git a/uip/inc.c b/uip/inc.c index bf382544..8a71eb22 100644 --- a/uip/inc.c +++ b/uip/inc.c @@ -43,10 +43,10 @@ #include #include -#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 \ @@ -70,10 +70,12 @@ X("version", 0, VERSIONSW) \ X("help", 0, HELPSW) \ X("snoop", 0, SNOOPSW) \ - X("sasl", SASLminc(5), SASLSW) \ - X("nosasl", SASLminc(3), NOSASLSW) \ - X("saslmech", SASLminc(5), SASLMECHSW) \ - X("authservice", SASLminc(0), AUTHSERVICESW) \ + X("sasl", 0, SASLSW) \ + X("nosasl", 0, NOSASLSW) \ + X("saslmech", 0, SASLMECHSW) \ + X("initialtls", TLSminc(-10), INITTLSSW) \ + X("notls", TLSminc(-5), NOTLSSW) \ + X("authservice", 0, AUTHSERVICESW) \ X("proxy command", 0, PROXYSW) \ #define X(sw, minchars, id) id, @@ -186,7 +188,7 @@ main (int argc, char **argv) int chgflag = 1, trnflag = 1; int noisy = 1, width = -1; int hghnum = 0, msgnum = 0; - int sasl = 0; + int sasl = 0, tls = 0; int incerr = 0; /* <0 if inc hits an error which means it should not truncate mailspool */ char *cp, *maildir = NULL, *folder = NULL; char *format = NULL, *form = NULL; @@ -234,13 +236,13 @@ main (int argc, char **argv) while ((cp = *argp++)) { if (*cp == '-') { switch (smatch (++cp, switches)) { - case AMBIGSW: + case AMBIGSW: ambigsw (cp, switches); done (1); - case UNKWNSW: + case UNKWNSW: adios (NULL, "-%s unknown", cp); - case HELPSW: + case HELPSW: snprintf (buf, sizeof(buf), "%s [+folder] [switches]", invo_name); print_help (buf, switches, 1); done (0); @@ -248,19 +250,19 @@ main (int argc, char **argv) print_version(invo_name); done (0); - case AUDSW: + case AUDSW: if (!(cp = *argp++) || *cp == '-') adios (NULL, "missing argument to %s", argp[-2]); audfile = getcpy (m_maildir (cp)); continue; - case NAUDSW: + case NAUDSW: audfile = NULL; continue; - case CHGSW: + case CHGSW: chgflag++; continue; - case NCHGSW: + case NCHGSW: chgflag = 0; continue; @@ -271,14 +273,14 @@ main (int argc, char **argv) * 1 by default (truncating is default) * 0 if -notruncate is given */ - case TRNCSW: + case TRNCSW: trnflag = 2; continue; - case NTRNCSW: + case NTRNCSW: trnflag = 0; continue; - case FILESW: + case FILESW: if (!(cp = *argp++) || *cp == '-') adios (NULL, "missing argument to %s", argp[-2]); from = path (cp, TFILE); @@ -291,25 +293,25 @@ main (int argc, char **argv) trnflag = 0; continue; - case SILSW: + case SILSW: noisy = 0; continue; - case NSILSW: + case NSILSW: noisy++; continue; - case FORMSW: + case FORMSW: if (!(form = *argp++) || *form == '-') adios (NULL, "missing argument to %s", argp[-2]); format = NULL; continue; - case FMTSW: + case FMTSW: if (!(format = *argp++) || *format == '-') adios (NULL, "missing argument to %s", argp[-2]); form = NULL; continue; - case WIDTHSW: + case WIDTHSW: if (!(cp = *argp++) || *cp == '-') adios (NULL, "missing argument to %s", argp[-2]); width = atoi (cp); @@ -354,6 +356,14 @@ main (int argc, char **argv) adios (NULL, "missing argument to %s", argp[-2]); continue; + case INITTLSSW: + tls++; + continue; + + case NOTLSSW: + tls = 0; + continue; + case AUTHSERVICESW: #ifdef OAUTH_SUPPORT if (!(auth_svc = *argp++) || *auth_svc == '-') @@ -421,7 +431,7 @@ main (int argc, char **argv) * initialize POP connection */ if (pop_init (host, port, creds.user, creds.password, proxy, snoop, - sasl, saslmech, auth_svc) == NOTOK) + sasl, saslmech, tls, auth_svc) == NOTOK) adios (NULL, "%s", response); /* Check if there are any messages */ @@ -657,7 +667,7 @@ go_to_it: switch (incerr = scan (pf, msgnum, 0, nfs, width, packfile ? 0 : msgnum == mp->hghmsg + 1 && chgflag, 1, NULL, stop - start, noisy, &scanl)) { - case SCNEOF: + case SCNEOF: printf ("%*d empty\n", DMAXFOLDER, msgnum); break; @@ -668,12 +678,12 @@ go_to_it: /* fall thru */ case SCNERR: - case SCNNUM: + case SCNNUM: break; - case SCNMSG: + case SCNMSG: case SCNENC: - default: + default: if (aud) fputs (charstring_buffer (scanl), aud); if (noisy) @@ -733,7 +743,7 @@ go_to_it: msgnum == hghnum && chgflag, 1, NULL, 0L, noisy, &scanl)) { case SCNFAT: - case SCNEOF: + case SCNEOF: break; case SCNERR: @@ -742,11 +752,11 @@ go_to_it: advise (NULL, "aborted!"); /* doesn't clean up locks! */ break; - case SCNNUM: + case SCNNUM: advise (NULL, "BUG in %s, number out of range", invo_name); break; - default: + default: advise (NULL, "BUG in %s, scan() botch (%d)", invo_name, incerr); break; @@ -807,7 +817,7 @@ go_to_it: } fclose (sf); sf = NULL; - } + } if (pf == NULL && (pf = fopen (cp, "r")) == NULL) adios (cp, "not available"); chmod (cp, m_gmprot ()); @@ -816,7 +826,7 @@ go_to_it: switch (incerr = scan (pf, msgnum, 0, nfs, width, msgnum == mp->hghmsg + 1 && chgflag, 1, NULL, stop - start, noisy, &scanl)) { - case SCNEOF: + case SCNEOF: printf ("%*d empty\n", DMAXFOLDER, msgnum); break; @@ -827,12 +837,12 @@ go_to_it: /* fall thru */ case SCNERR: - case SCNNUM: + case SCNNUM: break; - case SCNMSG: + case SCNMSG: case SCNENC: - default: + default: /* * Run the external program hook on the message. */ diff --git a/uip/mhparse.c b/uip/mhparse.c index d78ca7e5..7ec030ba 100644 --- a/uip/mhparse.c +++ b/uip/mhparse.c @@ -1751,7 +1751,7 @@ openBase64 (CT ct, char **file) /* sbeck -- handle suffixes */ CI ci; CE ce = &ct->c_cefile; - const char *decoded; + unsigned char *decoded; size_t decoded_len; unsigned char digest[16]; @@ -1840,7 +1840,7 @@ openBase64 (CT ct, char **file) if (decodeBase64 (buffer, &decoded, &decoded_len, ct->c_type == CT_TEXT, ct->c_digested ? digest : NULL) == OK) { size_t i; - const char *decoded_p = decoded; + unsigned char *decoded_p = decoded; for (i = 0; i < decoded_len; ++i) { putc (*decoded_p++, ce->ce_fp); } @@ -2907,13 +2907,13 @@ openURL (CT ct, char **file) static int readDigest (CT ct, char *cp) { - const char *digest; + unsigned char *digest; size_t len; if (decodeBase64 (cp, &digest, &len, 0, NULL) == OK) { const size_t maxlen = sizeof ct->c_digest / sizeof ct->c_digest[0]; - if (strlen (digest) <= maxlen) { + if (strlen ((char *) digest) <= maxlen) { memcpy (ct->c_digest, digest, maxlen); if (debugsw) { @@ -2930,7 +2930,7 @@ readDigest (CT ct, char *cp) } else { if (debugsw) { fprintf (stderr, "invalid MD5 digest (got %d octets)\n", - (int) strlen (digest)); + (int) strlen ((char *) digest)); } return NOTOK; diff --git a/uip/msgchk.c b/uip/msgchk.c index b6a5d809..5861dae9 100644 --- a/uip/msgchk.c +++ b/uip/msgchk.c @@ -350,7 +350,7 @@ remotemail (char *host, char *port, char *user, char *proxy, int notifysw, /* open the POP connection */ if (pop_init (host, port, creds.user, creds.password, proxy, snoop, sasl, - saslmech, auth_svc) == NOTOK + saslmech, 0, auth_svc) == NOTOK || pop_stat (&nmsgs, &nbytes) == NOTOK /* check for messages */ || pop_quit () == NOTOK) { /* quit POP connection */ advise (NULL, "%s", response); diff --git a/uip/popsbr.c b/uip/popsbr.c index 5b99c8cf..cf6b8042 100644 --- a/uip/popsbr.c +++ b/uip/popsbr.c @@ -9,18 +9,7 @@ #include #include #include - -#ifdef CYRUS_SASL -# include -# include -# 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 #include #include @@ -32,36 +21,7 @@ static int poprint = 0; static int pophack = 0; char response[BUFSIZ]; - -static FILE *input; -static FILE *output; - -#ifdef CYRUS_SASL -static sasl_conn_t *conn; /* SASL connection state */ -static int sasl_complete = 0; /* Has sasl authentication succeeded? */ -static int maxoutbuf; /* Maximum output buffer size */ -static sasl_ssf_t sasl_ssf = 0; /* Security strength factor */ -static int sasl_get_user(void *, int, const char **, unsigned *); -static int sasl_get_pass(sasl_conn_t *, void *, int, sasl_secret_t **); -struct pass_context { - char *user; - char *password; - char *host; -}; - -static sasl_callback_t callbacks[] = { - { SASL_CB_USER, (sasl_callback_ft) sasl_get_user, NULL }, -#define POP_SASL_CB_N_USER 0 - { SASL_CB_PASS, (sasl_callback_ft) sasl_get_pass, NULL }, -#define POP_SASL_CB_N_PASS 1 - { SASL_CB_LOG, NULL, NULL }, - { SASL_CB_LIST_END, NULL, NULL }, - -#define SASL_BUFFER_SIZE 262144 -}; -#else /* CYRUS_SASL */ -# define sasl_fgetc fgetc -#endif /* CYRUS_SASL */ +static netsec_context *nsc = NULL; /* * static prototypes @@ -70,19 +30,15 @@ static sasl_callback_t callbacks[] = { static int command(const char *, ...); static int multiline(void); -#ifdef CYRUS_SASL -static int pop_auth_sasl(char *, char *, char *); -static int sasl_fgetc(FILE *); -#endif /* CYRUS_SASL */ - static int traverse (int (*)(char *), const char *, ...); static int vcommand(const char *, va_list); -static int sasl_getline (char *, int, FILE *); -static int putline (char *, FILE *); +static int pop_getline (char *, int, netsec_context *); +static int pop_sasl_callback(enum sasl_message_type, unsigned const char *, + unsigned int, unsigned char **, unsigned int *, + char **); - -int -check_mech(char *server_mechs, size_t server_mechs_size, char *mech) +static int +check_mech(char *server_mechs, size_t server_mechs_size) { int status, sasl_capability = 0; @@ -125,337 +81,9 @@ check_mech(char *server_mechs, size_t server_mechs_size, char *mech) return NOTOK; } - /* - * If we received a preferred mechanism, see if the server supports it. - */ - - if (mech && stringdex(mech, server_mechs) == -1) { - snprintf(response, sizeof(response), "Requested SASL mech \"%s\" is " - "not in list of supported mechanisms:\n%s", - mech, server_mechs); - return NOTOK; - } - return OK; } -#ifdef CYRUS_SASL -/* - * This function implements the AUTH command for various SASL mechanisms - * - * We do the whole SASL dialog here. If this completes, then we've - * authenticated successfully and have (possibly) negotiated a security - * layer. - */ - -#define CHECKB64SIZE(insize, outbuf, outsize) \ - { size_t wantout = (((insize + 2) / 3) * 4) + 32; \ - if (wantout > outsize) { \ - outbuf = mh_xrealloc(outbuf, outsize = wantout); \ - } \ - } - -int -pop_auth_sasl(char *user, char *host, char *mech) -{ - int result, status; - unsigned int buflen, outlen; - char server_mechs[256], *buf, *outbuf = NULL; - size_t outbufsize = 0; - const char *chosen_mech; - sasl_security_properties_t secprops; - struct pass_context p_context; - sasl_ssf_t *ssf; - int *moutbuf; - - if ((status = check_mech(server_mechs, sizeof(server_mechs), mech)) != OK) { - return status; - } - - /* - * Start the SASL process. First off, initialize the SASL library. - */ - - callbacks[POP_SASL_CB_N_USER].context = user; - p_context.user = user; - p_context.host = host; - callbacks[POP_SASL_CB_N_PASS].context = &p_context; - - result = sasl_client_init(callbacks); - - if (result != SASL_OK) { - snprintf(response, sizeof(response), "SASL library initialization " - "failed: %s", sasl_errstring(result, NULL, NULL)); - return NOTOK; - } - - result = sasl_client_new("pop", host, NULL, NULL, NULL, 0, &conn); - - if (result != SASL_OK) { - snprintf(response, sizeof(response), "SASL client initialization " - "failed: %s", sasl_errstring(result, NULL, NULL)); - return NOTOK; - } - - /* - * Initialize the security properties - */ - - memset(&secprops, 0, sizeof(secprops)); - secprops.maxbufsize = SASL_BUFFER_SIZE; - secprops.max_ssf = UINT_MAX; - - result = sasl_setprop(conn, SASL_SEC_PROPS, &secprops); - - if (result != SASL_OK) { - snprintf(response, sizeof(response), "SASL security property " - "initialization failed: %s", sasl_errdetail(conn)); - return NOTOK; - } - - /* - * Start the actual protocol. Feed the mech list into the library - * and get out a possible initial challenge - */ - - result = sasl_client_start(conn, - (const char *) (mech ? mech : server_mechs), - NULL, (const char **) &buf, - &buflen, &chosen_mech); - - if (result != SASL_OK && result != SASL_CONTINUE) { - snprintf(response, sizeof(response), "SASL client start failed: %s", - sasl_errdetail(conn)); - return NOTOK; - } - - if (buflen) { - CHECKB64SIZE(buflen, outbuf, outbufsize); - status = sasl_encode64(buf, buflen, outbuf, outbufsize, NULL); - if (status != SASL_OK) { - snprintf(response, sizeof(response), "SASL base64 encode " - "failed: %s", sasl_errstring(status, NULL, NULL)); - if (outbuf) - free(outbuf); - return NOTOK; - } - - status = command("AUTH %s %s", chosen_mech, outbuf); - } else - status = command("AUTH %s", chosen_mech); - - while (result == SASL_CONTINUE) { - size_t inlen; - - if (status == NOTOK) { - if (outbuf) - free(outbuf); - return NOTOK; - } - - /* - * If we get a "+OK" prefix to our response, then we should - * exit out of this exchange now (because authenticated should - * have succeeded) - */ - - if (strncmp(response, "+OK", 3) == 0) - break; - - /* - * Otherwise, make sure the server challenge is correctly formatted - */ - - if (strncmp(response, "+ ", 2) != 0) { - command("*"); - snprintf(response, sizeof(response), - "Malformed authentication message from server"); - if (outbuf) - free(outbuf); - return NOTOK; - } - - /* - * For decode, it will always be shorter, so just make sure - * that outbuf is as at least as big as the encoded response. - */ - - inlen = strlen(response + 2); - - if (inlen > outbufsize) { - outbuf = mh_xrealloc(outbuf, outbufsize = inlen); - } - - result = sasl_decode64(response + 2, strlen(response + 2), - outbuf, outbufsize, &outlen); - - if (result != SASL_OK) { - command("*"); - snprintf(response, sizeof(response), "SASL base64 decode " - "failed: %s", sasl_errstring(result, NULL, NULL)); - if (outbuf) - free(outbuf); - return NOTOK; - } - - result = sasl_client_step(conn, outbuf, outlen, NULL, - (const char **) &buf, &buflen); - - if (result != SASL_OK && result != SASL_CONTINUE) { - command("*"); - snprintf(response, sizeof(response), "SASL client negotiaton " - "failed: %s", sasl_errdetail(conn)); - if (outbuf) - free(outbuf); - return NOTOK; - } - - CHECKB64SIZE(buflen, outbuf, outbufsize); - - status = sasl_encode64(buf, buflen, outbuf, outbufsize, NULL); - - if (status != SASL_OK) { - command("*"); - snprintf(response, sizeof(response), "SASL base64 encode " - "failed: %s", sasl_errstring(status, NULL, NULL)); - if (outbuf) - free(outbuf); - return NOTOK; - } - - status = command(outbuf); - } - - if (outbuf) - free(outbuf); - - /* - * If we didn't get a positive final response, then error out - * (that probably means we failed an authorization check). - */ - - if (status != OK) - return NOTOK; - - /* - * We _should_ be okay now. Get a few properties now that negotiation - * has completed. - */ - - result = sasl_getprop(conn, SASL_MAXOUTBUF, (const void **) &moutbuf); - - if (result != SASL_OK) { - snprintf(response, sizeof(response), "Cannot retrieve SASL negotiated " - "output buffer size: %s", sasl_errdetail(conn)); - return NOTOK; - } - - maxoutbuf = *moutbuf; - - result = sasl_getprop(conn, SASL_SSF, (const void **) &ssf); - - sasl_ssf = *ssf; - - if (result != SASL_OK) { - snprintf(response, sizeof(response), "Cannot retrieve SASL negotiated " - "security strength factor: %s", sasl_errdetail(conn)); - return NOTOK; - } - - /* - * Limit this to what we can deal with. Shouldn't matter much because - * this is only outgoing data (which should be small) - */ - - if (maxoutbuf == 0 || maxoutbuf > BUFSIZ) - maxoutbuf = BUFSIZ; - - sasl_complete = 1; - - return status; -} - -/* - * Callback to return the userid sent down via the user parameter - */ - -static int -sasl_get_user(void *context, int id, const char **result, unsigned *len) -{ - char *user = (char *) context; - - if (! result || id != SASL_CB_USER) - return SASL_BADPARAM; - - *result = user; - if (len) - *len = strlen(user); - - return SASL_OK; -} - -/* - * Callback to return the password (we call ruserpass, which can get it - * out of the .netrc - */ - -static int -sasl_get_pass(sasl_conn_t *conn, void *context, int id, sasl_secret_t **psecret) -{ - struct pass_context *p_context = (struct pass_context *) context; - struct nmh_creds creds = { 0, 0, 0 }; - int len; - - NMH_UNUSED (conn); - - if (! psecret || id != SASL_CB_PASS) - return SASL_BADPARAM; - - if (creds.password == NULL) { - /* - * Pass the 0 third argument to nmh_get_credentials() so - * that the default password isn't used. With legacy/.netrc - * credentials support, we'll only get here if the -user - * switch to send(1)/post(8) wasn't used. - */ - if (nmh_get_credentials (p_context->host, p_context->user, 0, &creds) - != OK) { - return SASL_BADPARAM; - } - } - - len = strlen (creds.password); - - *psecret = (sasl_secret_t *) mh_xmalloc(sizeof(sasl_secret_t) + len); - - (*psecret)->len = len; - strcpy((char *) (*psecret)->data, creds.password); - - return SASL_OK; -} -#endif /* CYRUS_SASL */ - -int -pop_auth_xoauth(const char *client_res) -{ - char server_mechs[256]; - int status = check_mech(server_mechs, sizeof(server_mechs), "XOAUTH"); - - if (status != OK) return status; - - if ((status = command("AUTH XOAUTH2 %s", client_res)) != OK) { - return status; - } - if (strncmp(response, "+OK", 3) == 0) { - return OK; - } - - /* response contains base64-encoded JSON, which is always the same. - * See mts/smtp/smtp.c for more notes on that. */ - /* Then we're supposed to send an empty response ("\r\n"). */ - return command(""); -} - /* * Split string containing proxy command into an array of arguments * suitable for passing to exec. Returned array must be freed. Shouldn't @@ -507,25 +135,23 @@ parse_proxy(char *proxy, char *host) int pop_init (char *host, char *port, char *user, char *pass, char *proxy, - int snoop, int sasl, char *mech, const char *oauth_svc) + int snoop, int sasl, char *mech, int tls, const char *oauth_svc) { int fd1, fd2; char buffer[BUFSIZ]; - const char *xoauth_client_res = NULL; -#ifndef CYRUS_SASL - NMH_UNUSED (sasl); - NMH_UNUSED (mech); -#endif /* ! CYRUS_SASL */ + char *errstr; + + nsc = netsec_init(); + + if (user) + netsec_set_userid(nsc, user); -#ifdef OAUTH_SUPPORT if (oauth_svc != NULL) { - xoauth_client_res = mh_oauth_do_xoauth(user, oauth_svc, - snoop ? stderr : NULL); + if (netsec_set_oauth_service(nsc, oauth_svc) != OK) { + snprintf(response, sizeof(response), "OAuth2 not supported"); + return NOTOK; + } } -#else - NMH_UNUSED (oauth_svc); - NMH_UNUSED (xoauth_client_res); -#endif /* OAUTH_SUPPORT */ if (proxy && *proxy) { int pid; @@ -575,47 +201,59 @@ pop_init (char *host, char *port, char *user, char *pass, char *proxy, fd2=outpipe[1]; } else { - if ((fd1 = client (host, port ? port : "pop3", response, sizeof(response), snoop)) == NOTOK) { return NOTOK; } + fd2 = fd1; + } - if ((fd2 = dup (fd1)) == NOTOK) { - char *s; + SIGNAL (SIGPIPE, SIG_IGN); + + netsec_set_fd(nsc, fd1, fd2); + netsec_set_snoop(nsc, snoop); + + if (tls) { + if (netsec_set_tls(nsc, 1, &errstr) != OK) { + snprintf(response, sizeof(response), "%s", errstr); + free(errstr); + return NOTOK; + } - if ((s = strerror(errno))) - snprintf (response, sizeof(response), - "unable to dup connection descriptor: %s", s); - else - snprintf (response, sizeof(response), - "unable to dup connection descriptor: unknown error"); - close (fd1); + if (netsec_negotiate_tls(nsc, &errstr) != OK) { + snprintf(response, sizeof(response), "%s", errstr); + free(errstr); return NOTOK; } } - if (pop_set (fd1, fd2, snoop) == NOTOK) - return NOTOK; - SIGNAL (SIGPIPE, SIG_IGN); + if (sasl) { + if (netsec_set_sasl_params(nsc, host, "pop", mech, + pop_sasl_callback, &errstr) != OK) { + snprintf(response, sizeof(response), "%s", errstr); + free(errstr); + return NOTOK; + } + } - switch (sasl_getline (response, sizeof response, input)) { + switch (pop_getline (response, sizeof response, nsc)) { case OK: if (poprint) fprintf (stderr, "<--- %s\n", response); if (*response == '+') { -# ifdef CYRUS_SASL if (sasl) { - if (pop_auth_sasl(user, host, mech) != NOTOK) - return OK; - } else -# endif /* CYRUS_SASL */ -# if OAUTH_SUPPORT - if (xoauth_client_res != NULL) { - if (pop_auth_xoauth(xoauth_client_res) != NOTOK) - return OK; + char server_mechs[256]; + if (check_mech(server_mechs, sizeof(server_mechs)) != OK) + return NOTOK; + if (netsec_negotiate_sasl(nsc, server_mechs, + &errstr) != OK) { + strncpy(response, errstr, sizeof(response)); + response[sizeof(response) - 1] = '\0'; + free(errstr); + return NOTOK; + } + return OK; } else -# endif /* OAUTH_SUPPORT */ if (command ("USER %s", user) != NOTOK && command ("%s %s", (pophack++, "PASS"), pass) != NOTOK) @@ -630,44 +268,190 @@ pop_init (char *host, char *port, char *user, char *pass, char *proxy, case DONE: if (poprint) fprintf (stderr, "%s\n", response); - fclose (input); - fclose (output); + netsec_shutdown(nsc, 1); + nsc = NULL; return NOTOK; } return NOTOK; /* NOTREACHED */ } -int -pop_set (int in, int out, int snoop) + +/* + * Our SASL callback; we are given SASL tokens and then have to format + * them according to the protocol requirements, and then process incoming + * messages and feed them back into the SASL library. + */ + +static int +pop_sasl_callback(enum sasl_message_type mtype, unsigned const char *indata, + unsigned int indatalen, unsigned char **outdata, + unsigned int *outdatalen, char **errstr) { + int rc, snoopoffset; + char *mech, *line; + size_t len, b64len; - if ((input = fdopen (in, "r")) == NULL - || (output = fdopen (out, "w")) == NULL) { - strncpy (response, "fdopen failed on connection descriptor", sizeof(response)); - if (input) - fclose (input); - else - close (in); - close (out); - return NOTOK; - } + switch (mtype) { + case NETSEC_SASL_START: + /* + * Generate our AUTH message, but there is a wrinkle. + * + * Technically, according to RFC 5034, if your command INCLUDING + * an initial response exceeds 255 octets (including CRLF), you + * can't issue this all in one go, but have to just issue the + * AUTH command, wait for a blank initial response, and then + * send your data. + */ - poprint = snoop; + mech = netsec_get_sasl_mechanism(nsc); + + if (indatalen) { + char *b64data; + b64data = mh_xmalloc(BASE64SIZE(indatalen)); + writeBase64raw(indata, indatalen, (unsigned char *) b64data); + b64len = strlen(b64data); + + /* Formula here is AUTH + SP + mech + SP + out + CR + LF */ + len = b64len + 8 + strlen(mech); + if (len > 255) { + rc = netsec_printf(nsc, errstr, "AUTH %s\r\n", mech); + if (rc) + return NOTOK; + if (netsec_flush(nsc, errstr) != OK) + return NOTOK; + line = netsec_readline(nsc, &len, errstr); + if (! line) + return NOTOK; + /* + * If the protocol is being followed correctly, should just + * be a "+ ", nothing else. + */ + if (len != 2 || strcmp(line, "+ ") != 0) { + netsec_err(errstr, "Did not get expected blank response " + "for initial challenge response"); + return NOTOK; + } + netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, NULL); + rc = netsec_printf(nsc, errstr, "%s\r\n", b64data); + netsec_set_snoop_callback(nsc, NULL, NULL); + free(b64data); + if (rc != OK) + return NOTOK; + if (netsec_flush(nsc, errstr) != OK) + return NOTOK; + } else { + netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, + &snoopoffset); + snoopoffset = 6 + strlen(mech); + rc = netsec_printf(nsc, errstr, "AUTH %s %s\r\n", mech, + b64data); + free(b64data); + netsec_set_snoop_callback(nsc, NULL, NULL); + if (rc != OK) + return NOTOK; + if (netsec_flush(nsc, errstr) != OK) + return NOTOK; + } + } else { + if (netsec_printf(nsc, errstr, "AUTH %s\r\n", mech) != OK) + return NOTOK; + if (netsec_flush(nsc, errstr) != OK) + return NOTOK; + } - return OK; -} + break; + /* + * We should get one line back, with our base64 data. Decode that + * and feed it back into the SASL library. + */ + case NETSEC_SASL_READ: + netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, &snoopoffset); + snoopoffset = 2; + line = netsec_readline(nsc, &len, errstr); + netsec_set_snoop_callback(nsc, NULL, NULL); + + if (line == NULL) + return NOTOK; + if (len < 2 || (len == 2 && strcmp(line, "+ ") != 0)) { + netsec_err(errstr, "Invalid format for SASL response"); + return NOTOK; + } + + if (len == 2) { + *outdata = NULL; + *outdatalen = 0; + } else { + rc = decodeBase64(line + 2, outdata, &len, 0, NULL); + *outdatalen = len; + if (rc != OK) { + netsec_err(errstr, "Unable to decode base64 response"); + return NOTOK; + } + } + break; + + /* + * Our encoding is pretty simple, so this is easy. + */ + + case NETSEC_SASL_WRITE: + if (indatalen == 0) { + rc = netsec_printf(nsc, errstr, "\r\n"); + } else { + unsigned char *b64data; + b64data = mh_xmalloc(BASE64SIZE(indatalen)); + writeBase64raw(indata, indatalen, b64data); + netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, NULL); + rc = netsec_printf(nsc, errstr, "%s\r\n", b64data); + netsec_set_snoop_callback(nsc, NULL, NULL); + free(b64data); + } + + if (rc != OK) + return NOTOK; + + if (netsec_flush(nsc, errstr) != OK) + return NOTOK; + break; + + /* + * Finish the protocol; we're looking for an +OK + */ + + case NETSEC_SASL_FINISH: + line = netsec_readline(nsc, &len, errstr); + if (line == NULL) + return NOTOK; + + if (strncmp(line, "+OK", 3) != 0) { + netsec_err(errstr, "Authentication failed: %s", line); + return NOTOK; + } + break; + + /* + * Cancel the SASL exchange in the middle of the commands; for + * POP, that's a single "*". + * + * It's unclear to me if I should be returning errors up; I finally + * decided the answer should be "yes", and if the upper layer wants to + * ignore them that's their choice. + */ + + case NETSEC_SASL_CANCEL: + rc = netsec_printf(nsc, errstr, "*\r\n"); + if (rc == OK) + rc = netsec_flush(nsc, errstr); + if (rc != OK) + return NOTOK; + break; + } -int -pop_fd (char *in, int inlen, char *out, int outlen) -{ - snprintf (in, inlen, "%d", fileno (input)); - snprintf (out, outlen, "%d", fileno (output)); return OK; } - /* * Find out number of messages available * and their total size. @@ -749,7 +533,7 @@ pop_retr (int msgno, int (*action)(char *)) static int traverse (int (*action)(char *), const char *fmt, ...) { - int result; + int result, snoopstate; va_list ap; char buffer[sizeof(response)]; @@ -761,13 +545,18 @@ traverse (int (*action)(char *), const char *fmt, ...) return NOTOK; strncpy (buffer, response, sizeof(buffer)); + if ((snoopstate = netsec_get_snoop(nsc))) + netsec_set_snoop(nsc, 0); + for (;;) switch (multiline ()) { case NOTOK: + netsec_set_snoop(nsc, snoopstate); return NOTOK; case DONE: strncpy (response, buffer, sizeof(response)); + netsec_set_snoop(nsc, snoopstate); return OK; case OK: @@ -820,12 +609,8 @@ pop_quit (void) int pop_done (void) { -#ifdef CYRUS_SASL - if (conn) - sasl_dispose(&conn); -#endif /* CYRUS_SASL */ - fclose (input); - fclose (output); + if (nsc) + netsec_shutdown(nsc, 1); return OK; } @@ -848,36 +633,31 @@ command(const char *fmt, ...) static int vcommand (const char *fmt, va_list ap) { - char *cp, buffer[65536]; - - vsnprintf (buffer, sizeof(buffer), fmt, ap); - - if (poprint) { -#ifdef CYRUS_SASL - if (sasl_ssf) - fprintf(stderr, "(encrypted) "); -#endif /* CYRUS_SASL */ - if (pophack) { - if ((cp = strchr (buffer, ' '))) - *cp = 0; - fprintf (stderr, "---> %s ********\n", buffer); - if (cp) - *cp = ' '; - pophack = 0; - } - else - fprintf (stderr, "---> %s\n", buffer); + /* char *cp; */ + char *errstr; + + if (netsec_vprintf(nsc, &errstr, fmt, ap) != OK) { + strncpy(response, errstr, sizeof(response)); + response[sizeof(response) - 1] = '\0'; + free(errstr); + return NOTOK; } - if (putline (buffer, output) == NOTOK) + if (netsec_printf(nsc, &errstr, "\r\n") != OK) { + strncpy(response, errstr, sizeof(response)); + response[sizeof(response) - 1] = '\0'; + free(errstr); return NOTOK; + } -#ifdef CYRUS_SASL - if (poprint && sasl_ssf) - fprintf(stderr, "(decrypted) "); -#endif /* CYRUS_SASL */ + if (netsec_flush(nsc, &errstr) != OK) { + strncpy(response, errstr, sizeof(response)); + response[sizeof(response) - 1] = '\0'; + free(errstr); + return NOTOK; + } - switch (sasl_getline (response, sizeof response, input)) { + switch (pop_getline (response, sizeof response, nsc)) { case OK: if (poprint) fprintf (stderr, "<--- %s\n", response); @@ -899,17 +679,8 @@ multiline (void) { char buffer[BUFSIZ + TRMLEN]; - if (sasl_getline (buffer, sizeof buffer, input) != OK) + if (pop_getline (buffer, sizeof buffer, nsc) != OK) return NOTOK; -#ifdef DEBUG - if (poprint) { -#ifdef CYRUS_SASL - if (sasl_ssf) - fprintf(stderr, "(decrypted) "); -#endif /* CYRUS_SASL */ - fprintf (stderr, "<--- %s\n", response); - } -#endif /* DEBUG */ if (strncmp (buffer, TRM, TRMLEN) == 0) { if (buffer[TRMLEN] == 0) return DONE; @@ -923,158 +694,42 @@ multiline (void) } /* - * Note that these functions have been modified to deal with layer encryption - * in the SASL case + * This is now just a thin wrapper around netsec_readline(). */ static int -sasl_getline (char *s, int n, FILE *iop) +pop_getline (char *s, int n, netsec_context *ns) { - int c = -2; + /* int c = -2; */ char *p; + size_t len, destlen; + /* int rc; */ + char *errstr; - p = s; - while (--n > 0 && (c = sasl_fgetc (iop)) != EOF && c != -2) - if ((*p++ = c) == '\n') - break; - if (c == -2) - return NOTOK; - if (ferror (iop) && c != EOF) { - strncpy (response, "error on connection", sizeof(response)); - return NOTOK; - } - if (c == EOF && p == s) { - strncpy (response, "connection closed by foreign host", sizeof(response)); - return DONE; - } - *p = 0; - if (*--p == '\n') - *p = 0; - if (p > s && *--p == '\r') - *p = 0; - - return OK; -} - - -static int -putline (char *s, FILE *iop) -{ -#ifdef CYRUS_SASL - char outbuf[BUFSIZ], *buf; - int result; - unsigned int buflen; - - if (!sasl_complete) { -#endif /* CYRUS_SASL */ - fprintf (iop, "%s\r\n", s); -#ifdef CYRUS_SASL - } else { - /* - * Build an output buffer, encrypt it using sasl_encode, and - * squirt out the results. - */ - strncpy(outbuf, s, sizeof(outbuf) - 3); - outbuf[sizeof(outbuf) - 3] = '\0'; /* Just in case */ - strcat(outbuf, "\r\n"); - - result = sasl_encode(conn, outbuf, strlen(outbuf), - (const char **) &buf, &buflen); + p = netsec_readline(ns, &len, &errstr); - if (result != SASL_OK) { - snprintf(response, sizeof(response), "SASL encoding error: %s", - sasl_errdetail(conn)); - return NOTOK; - } - - if (fwrite(buf, buflen, 1, iop) < 1) { - advise ("putline", "fwrite"); - } - } -#endif /* CYRUS_SASL */ - - fflush (iop); - if (ferror (iop)) { - strncpy (response, "lost connection", sizeof(response)); + if (p == NULL) { + strncpy(response, errstr, sizeof(response)); + response[sizeof(response) - 1] = '\0'; + free(errstr); return NOTOK; } - return OK; -} - -#ifdef CYRUS_SASL -/* - * Okay, our little fgetc replacement. Hopefully this is a little more - * efficient than the last one. - */ -static int -sasl_fgetc(FILE *f) -{ - static unsigned char *buffer = NULL, *ptr; - static unsigned int size = 0; - static int cnt = 0; - unsigned int retbufsize = 0; - int cc, result; - char *retbuf, tmpbuf[SASL_BUFFER_SIZE]; - - /* - * If we have some leftover data, return that - */ - - if (cnt) { - cnt--; - return (int) *ptr++; - } - /* - * Otherwise, fill our buffer until we have some data to return. + * If we had an error, it should have been returned already. Since + * netsec_readline() strips off the CR-LF ending, just copy the existing + * buffer into response now. + * + * We get a length back from netsec_readline, but the rest of the POP + * code doesn't handle it; the assumptions are that everything from + * the network can be respresented as C strings. That should get fixed + * someday. */ - while (retbufsize == 0) { - - cc = read(fileno(f), tmpbuf, sizeof(tmpbuf)); + destlen = len < ((size_t) (n - 1)) ? len : (size_t) (n - 1); - if (cc == 0) - return EOF; - - if (cc < 0) { - snprintf(response, sizeof(response), "Error during read from " - "network: %s", strerror(errno)); - return -2; - } - - /* - * We're not allowed to call sasl_decode until sasl_complete is - * true, so we do these gyrations ... - */ - - if (!sasl_complete) { - - retbuf = tmpbuf; - retbufsize = cc; - - } else { - - result = sasl_decode(conn, tmpbuf, cc, - (const char **) &retbuf, &retbufsize); - - if (result != SASL_OK) { - snprintf(response, sizeof(response), "Error during SASL " - "decoding: %s", sasl_errdetail(conn)); - return -2; - } - } - } - - if (retbufsize > size) { - buffer = mh_xrealloc(buffer, retbufsize); - size = retbufsize; - } - - memcpy(buffer, retbuf, retbufsize); - ptr = buffer + 1; - cnt = retbufsize - 1; - - return (int) buffer[0]; + memcpy(s, p, destlen); + s[destlen] = '\0'; + + return OK; } -#endif /* CYRUS_SASL */ diff --git a/uip/post.c b/uip/post.c index 47941afc..db9c0298 100644 --- a/uip/post.c +++ b/uip/post.c @@ -83,7 +83,6 @@ X("partno", -6, PARTSW) \ X("sasl", SASLminc(4), SASLSW) \ X("nosasl", SASLminc(6), NOSASLSW) \ - X("saslmaxssf", SASLminc(10), SASLMXSSFSW) \ X("saslmech", SASLminc(5), SASLMECHSW) \ X("user", SASLminc(-4), USERSW) \ X("port server submission port name/number", 4, PORTSW) \ @@ -231,7 +230,6 @@ static int checksw = 0; /* whom -check */ static int linepos=0; /* putadr()'s position on the line */ static int nameoutput=0; /* putadr() has output header name */ static int sasl=0; /* Use SASL auth for SMTP */ -static int saslssf=-1; /* Our maximum SSF for SASL */ static char *saslmech=NULL; /* Force use of particular SASL mech */ static char *user=NULL; /* Authenticate as this user */ static char *port="submission"; /* Name of server port for SMTP submission */ @@ -463,12 +461,6 @@ main (int argc, char **argv) sasl = 0; continue; - case SASLMXSSFSW: - if (!(cp = *argp++) || *cp == '-') - adios (NULL, "missing argument to %s", argp[-2]); - saslssf = atoi(cp); - continue; - case SASLMECHSW: if (!(saslmech = *argp++) || *saslmech == '-') adios (NULL, "missing argument to %s", argp[-2]); @@ -1655,8 +1647,7 @@ post (char *file, int bccque, int talk, int eai, char *envelope, } } else { if (rp_isbad (retval = sm_init (clientsw, serversw, port, watch, - verbose, snoop, sasl, saslssf, - saslmech, user, + verbose, snoop, sasl, saslmech, user, oauth_flag ? auth_svc : NULL, tls)) || rp_isbad (retval = sm_winit (envelope, eai))) { die (NULL, "problem initializing server; %s", rp_string (retval)); @@ -1698,8 +1689,7 @@ verify_all_addresses (int talk, int eai, char *envelope, int oauth_flag, if (!whomsw || checksw) { if (rp_isbad (retval = sm_init (clientsw, serversw, port, watch, - verbose, snoop, sasl, saslssf, - saslmech, user, + verbose, snoop, sasl, saslmech, user, oauth_flag ? auth_svc : NULL, tls)) || rp_isbad (retval = sm_winit (envelope, eai))) { die (NULL, "problem initializing server; %s", rp_string (retval)); diff --git a/uip/send.c b/uip/send.c index a4911989..1e48ea3a 100644 --- a/uip/send.c +++ b/uip/send.c @@ -63,7 +63,6 @@ X("snoop", 5, SNOOPSW) \ X("sasl", SASLminc(4), SASLSW) \ X("nosasl", SASLminc(6), NOSASLSW) \ - X("saslmaxssf", SASLminc(6), SASLMXSSFSW) \ X("saslmech mechanism", SASLminc(6), SASLMECHSW) \ X("authservice", SASLminc(0), AUTHSERVICESW) \ X("user username", SASLminc(-4), USERSW) \ @@ -287,7 +286,6 @@ main (int argc, char **argv) case WIDTHSW: case CLIESW: case SERVSW: - case SASLMXSSFSW: case PORTSW: case MTSSM: case MTSSW: diff --git a/uip/whatnowsbr.c b/uip/whatnowsbr.c index 80386e95..d6c336f6 100644 --- a/uip/whatnowsbr.c +++ b/uip/whatnowsbr.c @@ -947,7 +947,6 @@ buildfile (char **argp, char *file) X("nodraftfolder", 0, SNDRFSW) \ X("sasl", SASLminc(4), SASLSW) \ X("nosasl", SASLminc(6), NOSASLSW) \ - X("saslmaxssf", SASLminc(10), SASLMXSSFSW) \ X("saslmech", SASLminc(5), SASLMECHSW) \ X("authservice", SASLminc(0), AUTHSERVICESW) \ X("user username", SASLminc(4), USERSW) \ @@ -1165,7 +1164,6 @@ sendit (char *sp, char **arg, char *file, int pushed) case WIDTHSW: case CLIESW: case SERVSW: - case SASLMXSSFSW: case USERSW: case PORTSW: case MTSSM: