PROTON-334: SASL Implementation for Proton-C using Cyrus SASL This work Adds some new APIs to the transport and connection objects to make a higher level abstraction for authentication. This generally makes it much easier to use authentication.
It also vastly changes the Proton C API for SASL and deprecates nearly all of the previous interface that allowed reading and writing individual SASL frames. Project: http://git-wip-us.apache.org/repos/asf/qpid-proton/repo Commit: http://git-wip-us.apache.org/repos/asf/qpid-proton/commit/4a09c6a1 Tree: http://git-wip-us.apache.org/repos/asf/qpid-proton/tree/4a09c6a1 Diff: http://git-wip-us.apache.org/repos/asf/qpid-proton/diff/4a09c6a1 Branch: refs/heads/master Commit: 4a09c6a17f865df10f53fa61c8d2bc88d4627bb0 Parents: 450b8ba Author: Andrew Stitcher <[email protected]> Authored: Thu Apr 9 02:14:14 2015 -0400 Committer: Andrew Stitcher <[email protected]> Committed: Thu Apr 9 02:14:14 2015 -0400 ---------------------------------------------------------------------- proton-c/CMakeLists.txt | 24 +- proton-c/bindings/python/cproton.i | 14 - proton-c/bindings/python/proton/__init__.py | 74 +- proton-c/include/proton/connection.h | 39 + proton-c/include/proton/cproton.i | 67 +- proton-c/include/proton/event.h | 8 + proton-c/include/proton/sasl.h | 149 ++-- proton-c/include/proton/transport.h | 67 ++ proton-c/src/engine/engine-internal.h | 11 + proton-c/src/engine/engine.c | 22 + proton-c/src/events/event.c | 2 + proton-c/src/messenger/messenger.c | 16 +- proton-c/src/reactor/acceptor.c | 4 - proton-c/src/reactor/connection.c | 2 - proton-c/src/sasl/cyrus_sasl.c | 798 +++++++++++++++++++ proton-c/src/sasl/none_sasl.c | 425 ++++++++++ proton-c/src/sasl/sasl-internal.h | 8 +- proton-c/src/sasl/sasl.c | 445 +---------- proton-c/src/ssl/openssl.c | 7 + proton-c/src/tests/reactor.c | 4 +- proton-c/src/transport/transport.c | 96 ++- .../qpid/proton/engine/impl/SaslImpl.java | 7 +- proton-j/src/main/resources/csasl.py | 48 +- tests/python/proton_tests/sasl.py | 117 ++- tests/python/proton_tests/transport.py | 5 +- 25 files changed, 1681 insertions(+), 778 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4a09c6a1/proton-c/CMakeLists.txt ---------------------------------------------------------------------- diff --git a/proton-c/CMakeLists.txt b/proton-c/CMakeLists.txt index 4073f98..1b3f6d3 100644 --- a/proton-c/CMakeLists.txt +++ b/proton-c/CMakeLists.txt @@ -17,6 +17,7 @@ # under the License. # +include(CheckIncludeFiles) include(CheckLibraryExists) include(CheckSymbolExists) @@ -129,6 +130,25 @@ else (CLOCK_GETTIME_IN_LIBC) endif (CLOCK_GETTIME_IN_RT) endif (CLOCK_GETTIME_IN_LIBC) +# See if Cyrus SASL is desired and available +CHECK_LIBRARY_EXISTS (sasl2 sasl_checkpass "" FOUND_SASL_LIB) +CHECK_INCLUDE_FILES (sasl/sasl.h FOUND_SASL_H) +find_package_handle_standard_args(SASL DEFAULT_MSG FOUND_SASL_LIB FOUND_SASL_H) +set(sasl_providers cyrus none) +if (SASL_FOUND) + set (sasl_impl cyrus) +else (SASL_FOUND) + set (sasl_impl none) +endif (SASL_FOUND) +set(SASL_IMPL ${sasl_impl} CACHE STRING "Library to use for SSL/TLS support. Valid values: ${sasl_providers}") + +if (SASL_IMPL STREQUAL cyrus) + set(pn_sasl_impl src/sasl/sasl.c src/sasl/cyrus_sasl.c) + set(SASL_LIB "sasl2") +elseif (SASL_IMPL STREQUAL none) + set(pn_sasl_impl src/sasl/sasl.c src/sasl/none_sasl.c) +endif () + CHECK_SYMBOL_EXISTS(uuid_generate "uuid/uuid.h" UUID_GENERATE_IN_LIBC) if (UUID_GENERATE_IN_LIBC) list(APPEND PLATFORM_DEFINITIONS "USE_UUID_GENERATE") @@ -269,6 +289,7 @@ set (qpid-proton-platform ${pn_io_impl} ${pn_selector_impl} src/platform.c + ${pn_sasl_impl} ${pn_ssl_impl} ) @@ -301,7 +322,6 @@ set (qpid-proton-core src/transport/autodetect.c src/transport/transport.c src/message/message.c - src/sasl/sasl.c src/reactor/reactor.c src/reactor/handler.c @@ -343,7 +363,7 @@ add_library ( ${qpid-proton-platform} ) -target_link_libraries (qpid-proton ${UUID_LIB} ${SSL_LIB} ${TIME_LIB} ${PLATFORM_LIBS}) +target_link_libraries (qpid-proton ${UUID_LIB} ${SSL_LIB} ${SASL_LIB} ${TIME_LIB} ${PLATFORM_LIBS}) set_target_properties ( qpid-proton http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4a09c6a1/proton-c/bindings/python/cproton.i ---------------------------------------------------------------------- diff --git a/proton-c/bindings/python/cproton.i b/proton-c/bindings/python/cproton.i index 8efc2aa..70d6c42 100644 --- a/proton-c/bindings/python/cproton.i +++ b/proton-c/bindings/python/cproton.i @@ -158,20 +158,6 @@ ssize_t pn_data_decode(pn_data_t *data, char *STRING, size_t LENGTH); %} %ignore pn_data_encode; -%rename(pn_sasl_recv) wrap_pn_sasl_recv; -%inline %{ - int wrap_pn_sasl_recv(pn_sasl_t *sasl, char *OUTPUT, size_t *OUTPUT_SIZE) { - ssize_t sz = pn_sasl_recv(sasl, OUTPUT, *OUTPUT_SIZE); - if (sz >= 0) { - *OUTPUT_SIZE = sz; - } else { - *OUTPUT_SIZE = 0; - } - return sz; - } -%} -%ignore pn_sasl_recv; - int pn_data_format(pn_data_t *data, char *OUTPUT, size_t *OUTPUT_SIZE); %ignore pn_data_format; http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4a09c6a1/proton-c/bindings/python/proton/__init__.py ---------------------------------------------------------------------- diff --git a/proton-c/bindings/python/proton/__init__.py b/proton-c/bindings/python/proton/__init__.py index eeb7768..8a9d857 100644 --- a/proton-c/bindings/python/proton/__init__.py +++ b/proton-c/bindings/python/proton/__init__.py @@ -2401,6 +2401,20 @@ class Connection(Wrapper, Endpoint): hostname = property(_get_hostname, _set_hostname) + def _get_user(self): + return utf82unicode(pn_connection_get_user(self._impl)) + def _set_user(self, name): + return pn_connection_set_user(self._impl, unicode2utf8(name)) + + user = property(_get_user, _set_user) + + def _get_password(self): + return None + def _set_password(self, name): + return pn_connection_set_password(self._impl, unicode2utf8(name)) + + user = property(_get_password, _set_password) + @property def remote_container(self): """The container identifier specified by the remote peer for this connection.""" @@ -3162,6 +3176,20 @@ class Transport(Wrapper): else: return err + def require_auth(self, bool): + pn_transport_require_auth(self._impl, bool) + + @property + def authenticated(self): + return pn_transport_is_authenticated(self._impl) + + def require_encryption(self, bool): + pn_transport_require_encryption(self._impl, bool) + + @property + def encrypted(self): + return pn_transport_is_encrypted(self._impl) + def bind(self, connection): """Assign a connection to the transport""" self._check(pn_transport_bind(self._impl, connection._impl)) @@ -3298,7 +3326,6 @@ class SASL(Wrapper): OK = PN_SASL_OK AUTH = PN_SASL_AUTH - SKIPPED = PN_SASL_SKIPPED def __init__(self, transport): Wrapper.__init__(self, transport._impl, pn_transport_attachments) @@ -3311,39 +3338,6 @@ class SASL(Wrapper): else: return err - def mechanisms(self, mechs): - pn_sasl_mechanisms(self._sasl, mechs) - - # @deprecated - def client(self): - pn_sasl_client(self._sasl) - - # @deprecated - def server(self): - pn_sasl_server(self._sasl) - - def allow_skip(self, allow): - pn_sasl_allow_skip(self._sasl, allow) - - def plain(self, user, password): - pn_sasl_plain(self._sasl, user, password) - - def send(self, data): - self._check(pn_sasl_send(self._sasl, data, len(data))) - - def recv(self): - sz = 16 - while True: - n, data = pn_sasl_recv(self._sasl, sz) - if n == PN_OVERFLOW: - sz *= 2 - continue - elif n == PN_EOS: - return None - else: - self._check(n) - return data - @property def outcome(self): outcome = pn_sasl_outcome(self._sasl) @@ -3352,18 +3346,12 @@ class SASL(Wrapper): else: return outcome + def allowed_mechs(self, mechs): + pn_sasl_allowed_mechs(self._sasl, mechs) + def done(self, outcome): pn_sasl_done(self._sasl, outcome) - STATE_IDLE = PN_SASL_IDLE - STATE_STEP = PN_SASL_STEP - STATE_PASS = PN_SASL_PASS - STATE_FAIL = PN_SASL_FAIL - - @property - def state(self): - return pn_sasl_state(self._sasl) - class SSLException(TransportException): pass http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4a09c6a1/proton-c/include/proton/connection.h ---------------------------------------------------------------------- diff --git a/proton-c/include/proton/connection.h b/proton-c/include/proton/connection.h index fcaddf7..efc5a60 100644 --- a/proton-c/include/proton/connection.h +++ b/proton-c/include/proton/connection.h @@ -276,6 +276,45 @@ PN_EXTERN const char *pn_connection_get_container(pn_connection_t *connection); PN_EXTERN void pn_connection_set_container(pn_connection_t *connection, const char *container); /** + * Set the authentication username for a client connection + * + * It is necessary to set the username and password before binding the connection + * to a trasnport and it isn't allowed to change them after the binding. + * + * If not set then no authentication will be negotiated unless the client + * sasl layer is explicitly created (this would be for sometting like Kerberos + * where the credentials are implicit in the environment, or to explicitly use + * the ANONYMOUS SASL mechanism) + * + * @param[in] connection the connection + * @param[in] user the username + */ +PN_EXTERN void pn_connection_set_user(pn_connection_t *connection, const char *user); + +/** + * Set the authentication password for a client connection + * + * It is necessary to set the username and password before binding the connection + * to a trasnport and it isn't allowed to change them after the binding. + * + * Note that the password is write only and has no accessor as the underlying + * implementation should be zeroing the password after use to avoid the password + * being present in memory longer than necessary + * + * @param[in] connection the connection + * @param[in] password the password corresponding to the username - this will be copied and zeroed out after use + */ +PN_EXTERN void pn_connection_set_password(pn_connection_t *connection, const char *password); + +/** + * Get the authentication username for a client connection + * + * @param[in] connection the connection + * @return the username passed into the connection + */ +PN_EXTERN const char *pn_connection_get_user(pn_connection_t *connection); + +/** * Get the value of the AMQP Hostname used by a connection object. * * The pointer returned by this operation is valid until http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4a09c6a1/proton-c/include/proton/cproton.i ---------------------------------------------------------------------- diff --git a/proton-c/include/proton/cproton.i b/proton-c/include/proton/cproton.i index 844d763..ac2b121 100644 --- a/proton-c/include/proton/cproton.i +++ b/proton-c/include/proton/cproton.i @@ -81,11 +81,7 @@ typedef unsigned long int uintptr_t; %aggregate_check(int, check_sasl_outcome, PN_SASL_NONE, PN_SASL_OK, PN_SASL_AUTH, - PN_SASL_SYS, PN_SASL_PERM, PN_SASL_TEMP, PN_SASL_SKIPPED); - -%aggregate_check(int, check_sasl_state, - PN_SASL_IDLE, PN_SASL_STEP, - PN_SASL_PASS, PN_SASL_FAIL); + PN_SASL_SYS, PN_SASL_PERM, PN_SASL_TEMP); %contract pn_code(int code) @@ -881,71 +877,12 @@ typedef unsigned long int uintptr_t; pn_sasl != NULL; } -%contract pn_sasl_state(pn_sasl_t *sasl) -{ - require: - sasl != NULL; - ensure: - check_sasl_state(pn_sasl_state); -} - -%contract pn_sasl_mechanisms(pn_sasl_t *sasl, const char *mechanisms) -{ - require: - sasl != NULL; -} - -%contract pn_sasl_remote_mechanisms(pn_sasl_t *sasl) -{ - require: - sasl != NULL; -} - -%contract pn_sasl_client(pn_sasl_t *sasl) -{ - require: - sasl != NULL; -} - -%contract pn_sasl_server(pn_sasl_t *sasl) -{ - require: - sasl != NULL; -} - -%contract pn_sasl_allow_skip(pn_sasl_t *sasl, bool allow) -{ - require: - sasl != NULL; -} - -%contract pn_sasl_plain(pn_sasl_t *sasl, const char *username, const char *password) +%contract pn_sasl_allowed_mechs(pn_sasl_t *sasl, const char *mechanisms) { require: sasl != NULL; } -%contract pn_sasl_pending(pn_sasl_t *sasl) -{ - require: - sasl != NULL; -} - -%contract pn_sasl_recv(pn_sasl_t *sasl, char *bytes, size_t size) -{ - require: - sasl != NULL; - bytes != NULL; - size > 0; -} - -%contract pn_sasl_send(pn_sasl_t *sasl, const char *bytes, size_t size) -{ - require: - sasl != NULL; - bytes != NULL; - size > 0; -} %contract pn_sasl_done(pn_sasl_t *sasl, pn_sasl_outcome_t outcome) { http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4a09c6a1/proton-c/include/proton/event.h ---------------------------------------------------------------------- diff --git a/proton-c/include/proton/event.h b/proton-c/include/proton/event.h index 38aa121..ae559cc 100644 --- a/proton-c/include/proton/event.h +++ b/proton-c/include/proton/event.h @@ -263,6 +263,14 @@ typedef enum { PN_TRANSPORT, /** + * The transport has authenticated, if this is received by a server + * the associated transport has authenticated an incoming connection + * and pn_transport_get_user() can be used to obtain the authenticated + * user. + */ + PN_TRANSPORT_AUTHENTICATED, + + /** * Indicates that a transport error has occurred. Use * ::pn_transport_condition() to access the details of the error * from the associated transport. http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4a09c6a1/proton-c/include/proton/sasl.h ---------------------------------------------------------------------- diff --git a/proton-c/include/proton/sasl.h b/proton-c/include/proton/sasl.h index 104e156..d9600b4 100644 --- a/proton-c/include/proton/sasl.h +++ b/proton-c/include/proton/sasl.h @@ -55,136 +55,101 @@ typedef enum { PN_SASL_SYS=2, /** failed due to a system error */ PN_SASL_PERM=3, /** failed due to unrecoverable error */ PN_SASL_TEMP=4, /** failed due to transient error */ - PN_SASL_SKIPPED=5 /** the peer didn't perform the sasl exchange */ } pn_sasl_outcome_t; -/** The state of the SASL negotiation process */ -typedef enum { - PN_SASL_IDLE, /** Pending SASL Init */ - PN_SASL_STEP, /** negotiation in progress */ - PN_SASL_PASS, /** negotiation completed successfully */ - PN_SASL_FAIL /** negotiation failed */ -} pn_sasl_state_t; - /** Construct an Authentication and Security Layer object * - * @return a new SASL object representing the layer. + * This will return the SASL layer object for the supplied transport + * object. If there is currently no SASL layer one will be created. + * + * On the client side of an AMQP connection this will have the effect + * of ensuring that the AMQP SASL layer is used for that connection. + * + * @return an object representing the SASL layer. */ PN_EXTERN pn_sasl_t *pn_sasl(pn_transport_t *transport); -/** Access the current state of the layer. +/** Set the outcome of SASL negotiation * - * @param[in] sasl the layer to retrieve the state from. - * @return The state of the sasl layer. - */ -PN_EXTERN pn_sasl_state_t pn_sasl_state(pn_sasl_t *sasl); - -/** Set the acceptable SASL mechanisms for the layer. + * Used by the server to set the result of the negotiation process. * - * @param[in] sasl the layer to update - * @param[in] mechanisms a list of acceptable SASL mechanisms, - * separated by space + * @todo */ -PN_EXTERN void pn_sasl_mechanisms(pn_sasl_t *sasl, const char *mechanisms); +PN_EXTERN void pn_sasl_done(pn_sasl_t *sasl, pn_sasl_outcome_t outcome); -/** Retrieve the list of SASL mechanisms provided by the remote. +/** Retrieve the outcome of SASL negotiation. * - * @param[in] sasl the SASL layer. - * @return a string containing a list of the SASL mechanisms - * advertised by the remote (separated by spaces) + * @todo */ -PN_EXTERN const char *pn_sasl_remote_mechanisms(pn_sasl_t *sasl); +PN_EXTERN pn_sasl_outcome_t pn_sasl_outcome(pn_sasl_t *sasl); -/** - * @deprecated - * Configure the SASL layer to act as a SASL client. +/** Retrieve the authenticated user * - * This is now unnecessary, and performs no function. The server/clientness - * of the sasl layer is determined from the role of the transport that is used to create - * it. The API is retained here so as not to break existing code. + * This is usually used at the the server end to find the name of the authenticated user. + * On the client it will merely return whatever user was passed in to the + * pn_transport_set_user_password() API. * - * @param[in] sasl the SASL layer to configure as a client - */ -PN_EXTERN void pn_sasl_client(pn_sasl_t *sasl); - -/** - * @deprecated - * Configure the SASL layer to act as a server. + * If pn_sasl_outcome() returns a value other than PN_SASL_OK, then there will be no user to return. + * The returned value is only reliable after the PN_TRANSPORT_AUTHENTICATED event has been received. * - * This is now only necessary for backwards compatibility if creating a server pn_sasl_t - * from a pn_transport_t which was created implicitly as a client by pn_transport(). + * @param[in] sasl the sasl layer * - * @param[in] sasl the SASL layer to configure as a server + * @return + * If the SASL layer was not negotiated then 0 is returned + * If the ANONYMOUS mechanism is used then the user will be "anonymous" + * Otherwise a string containing the user is returned. */ -PN_EXTERN void pn_sasl_server(pn_sasl_t *sasl); +PN_EXTERN const char *pn_sasl_get_user(pn_sasl_t *sasl); -/** Configure a SASL server layer to permit the client to skip the SASL exchange. +/** Return the selected SASL mechanism * - * If the peer client skips the SASL exchange (i.e. goes right to the AMQP header) - * this server layer will succeed and result in the outcome of PN_SASL_SKIPPED. - * The default behavior is to fail and close the connection if the client skips - * SASL. + * The returned value is only reliable after the PN_TRANSPORT_AUTHENTICATED event has been received. * - * @param[in] sasl the SASL layer to configure - * @param[in] allow true -> allow skip; false -> forbid skip - */ -PN_EXTERN void pn_sasl_allow_skip(pn_sasl_t *sasl, bool allow); - -/** Configure the SASL layer to use the "PLAIN" mechanism. - * - * A utility function to configure a simple client SASL layer using - * PLAIN authentication. + * @param[in] sasl the SASL layer * - * @param[in] sasl the layer to configure. - * @param[in] username credential for the PLAIN authentication - * mechanism - * @param[in] password credential for the PLAIN authentication - * mechanism + * @return The authentication mechanism selected by the SASL layer */ -PN_EXTERN void pn_sasl_plain(pn_sasl_t *sasl, const char *username, const char *password); +PN_EXTERN const char *pn_sasl_get_mech(pn_sasl_t *sasl); -/** Determine the size of the bytes available via pn_sasl_recv(). +/** SASL mechanism that are not to be considered for authentication * - * Returns the size in bytes available via pn_sasl_recv(). + * This can be used on either the client or the server to restrict the SASL mechanisms that may be used. * - * @param[in] sasl the SASL layer. - * @return The number of bytes available, zero if no available data. + * @param[in] sasl the SASL layer + * @param[in] mechs space separated list of mechanisms that are not to be allowed for authentication */ -PN_EXTERN size_t pn_sasl_pending(pn_sasl_t *sasl); +PN_EXTERN void pn_sasl_allowed_mechs(pn_sasl_t *sasl, const char *mechs); -/** Read challenge/response data sent from the peer. +/** + * Set the sasl configuration name * - * Use pn_sasl_pending to determine the size of the data. + * This is used to construct the SASL configuration filename. In the current implementation + * it ".conf" is added to the name and the file is looked for in the configuration directory. * - * @param[in] sasl the layer to read from. - * @param[out] bytes written with up to size bytes of inbound data. - * @param[in] size maximum number of bytes that bytes can accept. - * @return The number of bytes written to bytes, or an error code if < 0. - */ -PN_EXTERN ssize_t pn_sasl_recv(pn_sasl_t *sasl, char *bytes, size_t size); - -/** Send challenge or response data to the peer. + * If not set it will default to "proton-server" for a sasl server and "proton-client" + * for a client. * - * @param[in] sasl The SASL layer. - * @param[in] bytes The challenge/response data. - * @param[in] size The number of data octets in bytes. - * @return The number of octets read from bytes, or an error code if < 0 + * @param[in] sasl the SASL layer + * @param[in] name the configuration name */ -PN_EXTERN ssize_t pn_sasl_send(pn_sasl_t *sasl, const char *bytes, size_t size); +PN_EXTERN void pn_sasl_config_name(pn_sasl_t *sasl, const char *name); -/** Set the outcome of SASL negotiation +/** + * Set the sasl configuration path * - * Used by the server to set the result of the negotiation process. + * This is used to tell SASL where to look for the configuration file. + * In the current implementation it can be a colon separated list of directories. * - * @todo - */ -PN_EXTERN void pn_sasl_done(pn_sasl_t *sasl, pn_sasl_outcome_t outcome); - -/** Retrieve the outcome of SASL negotiation. + * The environment variable PN_SASL_CONFIG_PATH can also be used to set this path, + * but if both methods are used then this pn_sasl_config_path() will take precedence. * - * @todo + * If not set the underlying implementation default will be used. + * for a client. + * + * @param[in] sasl the SASL layer + * @param[in] path the configuration path */ -PN_EXTERN pn_sasl_outcome_t pn_sasl_outcome(pn_sasl_t *sasl); +PN_EXTERN void pn_sasl_config_path(pn_sasl_t *sasl, const char *path); /** @} */ http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4a09c6a1/proton-c/include/proton/transport.h ---------------------------------------------------------------------- diff --git a/proton-c/include/proton/transport.h b/proton-c/include/proton/transport.h index 690952b..90244d5 100644 --- a/proton-c/include/proton/transport.h +++ b/proton-c/include/proton/transport.h @@ -121,6 +121,73 @@ PN_EXTERN void pn_transport_set_server(pn_transport_t *transport); */ PN_EXTERN void pn_transport_free(pn_transport_t *transport); +/** Retrieve the authenticated user + * + * This is usually used at the the server end to find the name of the authenticated user. + * On the client it will merely return whatever user was passed in to the + * pn_transport_set_user_password() API. + * + * The returned value is only reliable after the PN_TRANSPORT_AUTHENTICATED event has been received. + * + * @param[in] transport the transport + * + * @return + * If a the user is anonymous (either no SASL layer is negotiated or the SASL ANONYMOUS mechanism is used) + * then the user will be "anonymous" + * Otherwise a string containing the user is returned. + */ +PN_EXTERN const char *pn_transport_get_user(pn_transport_t *transport); + +/** + * Set whether a non authenticated transport connection is allowed + * + * There are several ways within the AMQP protocol suite to get unauthenticated connections: + * - Use no SASL layer (with either no TLS or TLS without client certificates) + * - Use an SASL layer but the ANONYMOUS mechanism + * + * The default if this option is not set is to allow unauthenticated connections. + * + * @param[in] transport the transport + * @param[in] required boolean is true when authenticated connections are required + */ +PN_EXTERN void pn_transport_require_auth(pn_transport_t *transport, bool required); + +/** + * Tell whether the transport connection is authenticated + * + * Note that this property may not be stable until the PN_CONNECTION_REMOTE_OPEN + * event is received. + * + * @param[in] transport the transport + * @return bool representing authentication + */ +PN_EXTERN bool pn_transport_is_authenticated(pn_transport_t *transport); + +/** + * Set whether a non encrypted transport connection is allowed + * + * There are several ways within the AMQP protocol suite to get encrypted connections: + * - Use TLS/SSL + * - Use an SASL with a mechanism that supports saecurity layers + * + * The default if this option is not set is to allow unencrypted connections. + * + * @param[in] transport the transport + * @param[in] required boolean is true when encrypted connections are required + */ +PN_EXTERN void pn_transport_require_encryption(pn_transport_t *transport, bool required); + +/** + * Tell whether the transport connection is encrypted + * + * Note that this property may not be stable until the PN_CONNECTION_REMOTE_OPEN + * event is received. + * + * @param[in] transport the transport + * @return bool representing encryption + */ +PN_EXTERN bool pn_transport_is_encrypted(pn_transport_t *transport); + /** * Get additional information about the condition of the transport. * http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4a09c6a1/proton-c/src/engine/engine-internal.h ---------------------------------------------------------------------- diff --git a/proton-c/src/engine/engine-internal.h b/proton-c/src/engine/engine-internal.h index e5ec602..035d5be 100644 --- a/proton-c/src/engine/engine-internal.h +++ b/proton-c/src/engine/engine-internal.h @@ -194,8 +194,16 @@ struct pn_transport_t { bool posted_idle_timeout; bool server; bool halt; + bool auth_required; + bool authenticated; + bool encryption_required; + bool encrypted; bool referenced; + + // Temporarily here until refactor + bool sasl_input_bypass; + bool sasl_output_bypass; }; struct pn_connection_t { @@ -213,6 +221,8 @@ struct pn_connection_t { pn_delivery_t *tpwork_tail; pn_string_t *container; pn_string_t *hostname; + pn_string_t *auth_user; + pn_string_t *auth_password; pn_data_t *offered_capabilities; pn_data_t *desired_capabilities; pn_data_t *properties; @@ -331,6 +341,7 @@ void pn_clear_modified(pn_connection_t *connection, pn_endpoint_t *endpoint); void pn_connection_bound(pn_connection_t *conn); void pn_connection_unbound(pn_connection_t *conn); int pn_do_error(pn_transport_t *transport, const char *condition, const char *fmt, ...); +void pn_set_error_layer(pn_transport_t *transport); void pn_session_bound(pn_session_t* ssn); void pn_session_unbound(pn_session_t* ssn); void pn_link_bound(pn_link_t* link); http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4a09c6a1/proton-c/src/engine/engine.c ---------------------------------------------------------------------- diff --git a/proton-c/src/engine/engine.c b/proton-c/src/engine/engine.c index 5e05cbc..2454aa2 100644 --- a/proton-c/src/engine/engine.c +++ b/proton-c/src/engine/engine.c @@ -474,6 +474,8 @@ static void pn_connection_finalize(void *object) pn_free(conn->container); pn_free(conn->hostname); + pn_free(conn->auth_user); + pn_free(conn->auth_password); pn_free(conn->offered_capabilities); pn_free(conn->desired_capabilities); pn_free(conn->properties); @@ -506,6 +508,8 @@ pn_connection_t *pn_connection(void) conn->tpwork_tail = NULL; conn->container = pn_string(NULL); conn->hostname = pn_string(NULL); + conn->auth_user = pn_string(NULL); + conn->auth_password = pn_string(NULL); conn->offered_capabilities = pn_data(0); conn->desired_capabilities = pn_data(0); conn->properties = pn_data(0); @@ -568,6 +572,24 @@ void pn_connection_set_hostname(pn_connection_t *connection, const char *hostnam pn_string_set(connection->hostname, hostname); } +const char *pn_connection_get_user(pn_connection_t *connection) +{ + assert(connection); + return pn_string_get(connection->auth_user); +} + +void pn_connection_set_user(pn_connection_t *connection, const char *user) +{ + assert(connection); + pn_string_set(connection->auth_user, user); +} + +void pn_connection_set_password(pn_connection_t *connection, const char *password) +{ + assert(connection); + pn_string_set(connection->auth_password, password); +} + pn_data_t *pn_connection_offered_capabilities(pn_connection_t *connection) { assert(connection); http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4a09c6a1/proton-c/src/events/event.c ---------------------------------------------------------------------- diff --git a/proton-c/src/events/event.c b/proton-c/src/events/event.c index 6d24f0f..611c61b 100644 --- a/proton-c/src/events/event.c +++ b/proton-c/src/events/event.c @@ -327,6 +327,8 @@ const char *pn_event_type_name(pn_event_type_t type) return "PN_DELIVERY"; case PN_TRANSPORT: return "PN_TRANSPORT"; + case PN_TRANSPORT_AUTHENTICATED: + return "PN_TRANSPORT_AUTHENTICATED"; case PN_TRANSPORT_ERROR: return "PN_TRANSPORT_ERROR"; case PN_TRANSPORT_HEAD_CLOSED: http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4a09c6a1/proton-c/src/messenger/messenger.c ---------------------------------------------------------------------- diff --git a/proton-c/src/messenger/messenger.c b/proton-c/src/messenger/messenger.c index abc76a4..f226f7b 100644 --- a/proton-c/src/messenger/messenger.c +++ b/proton-c/src/messenger/messenger.c @@ -330,10 +330,6 @@ static void pni_listener_readable(pn_selectable_t *sel) pn_ssl_t *ssl = pn_ssl(t); pn_ssl_init(ssl, ctx->domain, NULL); - pn_sasl_t *sasl = pn_sasl(t); - - pn_sasl_mechanisms(sasl, "ANONYMOUS"); - pn_sasl_done(sasl, PN_SASL_OK); pn_connection_t *conn = pn_messenger_connection(ctx->messenger, sock, scheme, NULL, NULL, NULL, NULL, ctx); pn_transport_bind(t, conn); @@ -916,19 +912,9 @@ static int pn_transport_config(pn_messenger_t *messenger, } pn_ssl_t *ssl = pn_ssl(transport); pn_ssl_init(ssl, d, NULL); - pn_ssl_set_peer_hostname(ssl, pn_connection_get_hostname(connection)); pn_ssl_domain_free( d ); } - if (ctx->user) { - pn_sasl_t *sasl = pn_sasl(transport); - if (ctx->pass) { - pn_sasl_plain(sasl, ctx->user, ctx->pass); - } else { - pn_sasl_mechanisms(sasl, "ANONYMOUS"); - } - } - return 0; } @@ -1073,6 +1059,8 @@ pn_connection_t *pn_messenger_connection(pn_messenger_t *messenger, pn_connection_set_container(connection, messenger->name); pn_connection_set_hostname(connection, host); + pn_connection_set_user(connection, user); + pn_connection_set_password(connection, pass); pn_list_add(messenger->connections, connection); http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4a09c6a1/proton-c/src/reactor/acceptor.c ---------------------------------------------------------------------- diff --git a/proton-c/src/reactor/acceptor.c b/proton-c/src/reactor/acceptor.c index 952e16f..f7202d4 100644 --- a/proton-c/src/reactor/acceptor.c +++ b/proton-c/src/reactor/acceptor.c @@ -39,10 +39,6 @@ void pni_acceptor_readable(pn_selectable_t *sel) { pn_connection_t *conn = pn_reactor_connection(reactor, handler); pn_transport_t *trans = pn_transport(); pn_transport_set_server(trans); - pn_sasl_t *sasl = pn_sasl(trans); - pn_sasl_allow_skip(sasl, true); - pn_sasl_mechanisms(sasl, "ANONYMOUS"); - pn_sasl_done(sasl, PN_SASL_OK); pn_transport_bind(trans, conn); pn_decref(trans); pn_reactor_selectable_transport(reactor, sock, trans); http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4a09c6a1/proton-c/src/reactor/connection.c ---------------------------------------------------------------------- diff --git a/proton-c/src/reactor/connection.c b/proton-c/src/reactor/connection.c index 3560fc5..a6e7c5a 100644 --- a/proton-c/src/reactor/connection.c +++ b/proton-c/src/reactor/connection.c @@ -101,8 +101,6 @@ void pni_handle_open(pn_reactor_t *reactor, pn_event_t *event) { } pn_transport_t *transport = pn_transport(); - pn_sasl_t *sasl = pn_sasl(transport); - pn_sasl_mechanisms(sasl, "ANONYMOUS"); pn_transport_bind(transport, conn); pn_decref(transport); } http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4a09c6a1/proton-c/src/sasl/cyrus_sasl.c ---------------------------------------------------------------------- diff --git a/proton-c/src/sasl/cyrus_sasl.c b/proton-c/src/sasl/cyrus_sasl.c new file mode 100644 index 0000000..9e26517 --- /dev/null +++ b/proton-c/src/sasl/cyrus_sasl.c @@ -0,0 +1,798 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "sasl-internal.h" + +#include "protocol.h" +#include "dispatch_actions.h" +#include "engine/engine-internal.h" +#include "proton/codec.h" + +#include <sasl/sasl.h> + +enum pni_sasl_state { + SASL_NONE, + SASL_POSTED_INIT, + SASL_POSTED_MECHANISMS, + SASL_POSTED_RESPONSE, + SASL_POSTED_CHALLENGE, + SASL_PRETEND_OUTCOME, + SASL_RECVED_OUTCOME, + SASL_POSTED_OUTCOME +}; + +struct pni_sasl_t { + // Client selected mechanism + char *selected_mechanism; + char *included_mechanisms; + const char *username; + char *password; + const char *config_name; + char *config_dir; + const char *remote_fqdn; + sasl_conn_t *cyrus_conn; + pn_sasl_outcome_t outcome; + pn_bytes_t cyrus_out; + enum pni_sasl_state desired_state; + enum pni_sasl_state last_state; + bool client; + bool halt; +}; + +static bool pni_sasl_is_server_state(enum pni_sasl_state state) +{ + return state==SASL_NONE + || state==SASL_POSTED_MECHANISMS + || state==SASL_POSTED_CHALLENGE + || state==SASL_POSTED_OUTCOME; +} + +static bool pni_sasl_is_client_state(enum pni_sasl_state state) +{ + return state==SASL_NONE + || state==SASL_POSTED_INIT + || state==SASL_POSTED_RESPONSE + || state==SASL_PRETEND_OUTCOME + || state==SASL_RECVED_OUTCOME; +} + +static bool pni_sasl_is_final_input_state(pni_sasl_t *sasl) +{ + enum pni_sasl_state last_state = sasl->last_state; + enum pni_sasl_state desired_state = sasl->desired_state; + return last_state==SASL_RECVED_OUTCOME + || desired_state==SASL_POSTED_OUTCOME; +} + +static bool pni_sasl_is_final_output_state(pni_sasl_t *sasl) +{ + enum pni_sasl_state last_state = sasl->last_state; + return last_state==SASL_PRETEND_OUTCOME + || last_state==SASL_RECVED_OUTCOME + || last_state==SASL_POSTED_OUTCOME; +} + +static const char *amqp_service = "amqp"; + +static inline pn_transport_t *get_transport_internal(pn_sasl_t *sasl) +{ + // The external pn_sasl_t is really a pointer to the internal pni_transport_t + return ((pn_transport_t *)sasl); +} + +static inline pni_sasl_t *get_sasl_internal(pn_sasl_t *sasl) +{ + // The external pn_sasl_t is really a pointer to the internal pni_transport_t + return sasl ? ((pn_transport_t *)sasl)->sasl : NULL; +} + +static void pni_emit(pn_transport_t *transport) +{ + if (transport->connection && transport->connection->collector) { + pn_collector_t *collector = transport->connection->collector; + pn_collector_put(collector, PN_OBJECT, transport, PN_TRANSPORT); + } +} + +// Look for symbol in the mech include list - not particlarly efficient, +// but probably not used enough to matter. +// +// Note that if there is no inclusion list then every mech is implicitly included. +static bool pni_included_mech(const char *included_mech_list, pn_bytes_t s) +{ + if (!included_mech_list) return true; + + const char * end_list = included_mech_list+strlen(included_mech_list); + size_t len = s.size; + const char *c = included_mech_list; + while (c!=NULL) { + // If there are not enough chars left in the list no matches + if ((ptrdiff_t)len > end_list-c) return false; + + // Is word equal with a space or end of string afterwards? + if (strncasecmp(c, s.start, len)==0 && (c[len]==' ' || c[len]==0) ) return true; + + c = strchr(c, ' '); + c = c ? c+1 : NULL; + } + return false; +} + +// This takes a space separated list and zero terminates it in place +// whilst adding pointers to the existing strings in a string array. +// This means that you can't free the original storage until you have +// finished with the resulting list. +static void pni_split_mechs(char *mechlist, const char* included_mechs, char *mechs[], int *count) +{ + char *start = mechlist; + char *end = start; + + while (*end) { + if (*end == ' ') { + if (start != end) { + *end = '\0'; + if (pni_included_mech(included_mechs, pn_bytes(end-start, start))) { + mechs[(*count)++] = start; + } + } + end++; + start = end; + } else { + end++; + } + } + + if (start != end) { + if (pni_included_mech(included_mechs, pn_bytes(end-start, start))) { + mechs[(*count)++] = start; + } + } +} + +static bool pni_check_sasl_result(sasl_conn_t *conn, int r, pn_transport_t *logger) +{ + if (r!=SASL_OK) { + pn_transport_logf(logger, "sasl error: %s", conn ? sasl_errdetail(conn) : sasl_errstring(r, NULL, NULL)); + return false; + } + return true; +} + +// Cyrus wrappers +static void pni_cyrus_interact(pni_sasl_t *sasl, sasl_interact_t *interact) +{ + for (sasl_interact_t *i = interact; i->id!=SASL_CB_LIST_END; i++) { + switch (i->id) { + case SASL_CB_USER: + i->result = sasl->username; + i->len = strlen(sasl->username); + break; + case SASL_CB_AUTHNAME: + i->result = sasl->username; + i->len = strlen(sasl->username); + break; + case SASL_CB_PASS: + i->result = sasl->password; + i->len = strlen(sasl->password); + break; + default: + fprintf(stderr, "(%s): %s - %s\n", i->challenge, i->prompt, i->defresult); + } + } +} + +static void pni_sasl_set_desired_state(pn_transport_t *transport, enum pni_sasl_state desired_state) +{ + pni_sasl_t *sasl = transport->sasl; + if (sasl->last_state > desired_state) { + pn_transport_logf(transport, "Trying to send SASL frame (%d), but illegal: already in later state (%d)", desired_state, sasl->last_state); + } else if (sasl->client && !pni_sasl_is_client_state(desired_state)) { + pn_transport_logf(transport, "Trying to send server SASL frame (%d) on a client", desired_state); + } else if (!sasl->client && !pni_sasl_is_server_state(desired_state)) { + pn_transport_logf(transport, "Trying to send client SASL frame (%d) on a server", desired_state); + } else { + // If we need to repeat CHALLENGE or RESPONSE frames adjust current state to seem + // like they haven't been sent yet + if (sasl->last_state==desired_state && desired_state==SASL_POSTED_RESPONSE) { + sasl->last_state = SASL_POSTED_INIT; + } + if (sasl->last_state==desired_state && desired_state==SASL_POSTED_CHALLENGE) { + sasl->last_state = SASL_POSTED_MECHANISMS; + } + sasl->desired_state = desired_state; + } +} + +// Post SASL frame +static void pni_post_sasl_frame(pn_transport_t *transport) +{ + pni_sasl_t *sasl = transport->sasl; + pn_bytes_t out = sasl->cyrus_out; + enum pni_sasl_state desired_state = sasl->desired_state; + while (sasl->desired_state > sasl->last_state) { + switch (desired_state) { + case SASL_POSTED_INIT: + pn_post_frame(transport, SASL_FRAME_TYPE, 0, "DL[sz]", SASL_INIT, sasl->selected_mechanism, + out.size, out.start); + break; + case SASL_PRETEND_OUTCOME: + if (sasl->last_state < SASL_POSTED_INIT) { + desired_state = SASL_POSTED_INIT; + continue; + } + break; + case SASL_POSTED_MECHANISMS: { + // TODO: Hardcoded limit of 16 mechanisms + char *mechs[16]; + int count = 0; + + char *mechlist = NULL; + if (sasl->cyrus_conn) { + const char *result = NULL; + + int r = sasl_listmech(sasl->cyrus_conn, NULL, "", " ", "", &result, NULL, NULL); + if (pni_check_sasl_result(sasl->cyrus_conn, r, transport)) { + if (result && *result) { + mechlist = strdup(result); + pni_split_mechs(mechlist, sasl->included_mechanisms, mechs, &count); + } + } + } + pn_post_frame(transport, SASL_FRAME_TYPE, 0, "DL[@T[*s]]", SASL_MECHANISMS, PN_SYMBOL, count, mechs); + free(mechlist); + break; + } + case SASL_POSTED_RESPONSE: + pn_post_frame(transport, SASL_FRAME_TYPE, 0, "DL[z]", SASL_RESPONSE, out.size, out.start); + break; + case SASL_POSTED_CHALLENGE: + if (sasl->last_state < SASL_POSTED_MECHANISMS) { + desired_state = SASL_POSTED_MECHANISMS; + continue; + } + pn_post_frame(transport, SASL_FRAME_TYPE, 0, "DL[z]", SASL_CHALLENGE, out.size, out.start); + break; + case SASL_POSTED_OUTCOME: + if (sasl->last_state < SASL_POSTED_MECHANISMS) { + desired_state = SASL_POSTED_MECHANISMS; + continue; + } + pn_post_frame(transport, SASL_FRAME_TYPE, 0, "DL[B]", SASL_OUTCOME, sasl->outcome); + break; + case SASL_NONE: + case SASL_RECVED_OUTCOME: + return; + } + sasl->last_state = desired_state; + desired_state = sasl->desired_state; + } + pni_emit(transport); +} + +// Set up callbacks to use interact +static const sasl_callback_t pni_user_password_callbacks[] = { + {SASL_CB_USER, NULL, NULL}, + {SASL_CB_AUTHNAME, NULL, NULL}, + {SASL_CB_PASS, NULL, NULL}, + {SASL_CB_LIST_END, NULL, NULL}, +}; + +static const sasl_callback_t pni_user_callbacks[] = { + {SASL_CB_USER, NULL, NULL}, + {SASL_CB_AUTHNAME, NULL, NULL}, + {SASL_CB_LIST_END, NULL, NULL}, +}; + +static int pni_wrap_client_start(pni_sasl_t *sasl, const char *mechs, const char **mechusing) +{ + int result; + + if (sasl->config_dir) { + result = sasl_set_path(SASL_PATH_TYPE_CONFIG, sasl->config_dir); + if (result!=SASL_OK) return result; + } + + result = sasl_client_init(NULL); + if (result!=SASL_OK) return result; + + const sasl_callback_t *callbacks = sasl->username ? sasl->password ? pni_user_password_callbacks : pni_user_callbacks : NULL; + result = sasl_client_new(amqp_service, + sasl->remote_fqdn, + NULL, NULL, + callbacks, 0, + &sasl->cyrus_conn); + if (result!=SASL_OK) return result; + + sasl_interact_t *client_interact=NULL; + const char *out; + unsigned outlen; + + do { + + result = sasl_client_start(sasl->cyrus_conn, + mechs, + &client_interact, + &out, &outlen, + mechusing); + if (result==SASL_INTERACT) { + pni_cyrus_interact(sasl, client_interact); + } + } while (result==SASL_INTERACT); + + sasl->cyrus_out.start = out; + sasl->cyrus_out.size = outlen; + return result; +} + +static void pni_process_mechanisms(pn_transport_t *transport, const char *mechs, bool short_circuit) +{ + pni_sasl_t *sasl = transport->sasl; + const char *mech_selected; + int result = pni_wrap_client_start(sasl, mechs, &mech_selected); + switch (result) { + case SASL_OK: + case SASL_CONTINUE: + sasl->selected_mechanism = strdup(mech_selected); + pni_sasl_set_desired_state(transport, short_circuit ? SASL_PRETEND_OUTCOME : SASL_POSTED_INIT); + break; + case SASL_NOMECH: + default: + pni_check_sasl_result(sasl->cyrus_conn, result, transport); + sasl->last_state = SASL_RECVED_OUTCOME; + sasl->halt = true; + pn_transport_close_tail(transport); + break; + } +} + + +static int pni_wrap_client_step(pni_sasl_t *sasl, const pn_bytes_t *in) +{ + sasl_interact_t *client_interact=NULL; + const char *out; + unsigned outlen; + + int result; + do { + + result = sasl_client_step(sasl->cyrus_conn, + in->start, in->size, + &client_interact, + &out, &outlen); + if (result==SASL_INTERACT) { + pni_cyrus_interact(sasl, client_interact); + } + } while (result==SASL_INTERACT); + + sasl->cyrus_out.start = out; + sasl->cyrus_out.size = outlen; + return result; +} + +static void pni_process_challenge(pn_transport_t *transport, const pn_bytes_t *recv) +{ + pni_sasl_t *sasl = transport->sasl; + int result = pni_wrap_client_step(sasl, recv); + switch (result) { + case SASL_OK: + // Authenticated + // TODO: Documented that we need to call sasl_client_step() again to be sure!; + case SASL_CONTINUE: + // Need to send a response + pni_sasl_set_desired_state(transport, SASL_POSTED_RESPONSE); + break; + default: + pni_check_sasl_result(sasl->cyrus_conn, result, transport); + + // Failed somehow + sasl->halt = true; + pn_transport_close_tail(transport); + break; + } +} + +static int pni_wrap_server_new(pn_transport_t *transport) +{ + pni_sasl_t *sasl = transport->sasl; + int result; + + if (sasl->config_dir) { + result = sasl_set_path(SASL_PATH_TYPE_CONFIG, sasl->config_dir); + if (result!=SASL_OK) return result; + } + + result = sasl_server_init(NULL, sasl->config_name); + if (result!=SASL_OK) return result; + + result = sasl_server_new(amqp_service, NULL, NULL, NULL, NULL, NULL, 0, &sasl->cyrus_conn); + if (result!=SASL_OK) return result; + + sasl_security_properties_t secprops = {0}; + secprops.security_flags = + SASL_SEC_NOPLAINTEXT | + ( transport->auth_required ? SASL_SEC_NOANONYMOUS : 0 ) ; + + result = sasl_setprop(sasl->cyrus_conn, SASL_SEC_PROPS, &secprops); + if (result!=SASL_OK) return result; + + // EXTERNAL not implemented yet +#if 0 + sasl_ssf_t ssf = 128; + result = sasl_setprop(sasl->cyrus_conn, SASL_SSF_EXTERNAL, &ssf); + if (result!=SASL_OK) return result; + + const char *extid = "user"; + result = sasl_setprop(sasl->cyrus_conn, SASL_AUTH_EXTERNAL, extid); + if (result!=SASL_OK) return result; +#endif + + return result; +} + +static int pni_wrap_server_start(pni_sasl_t *sasl, const char *mech_selected, const pn_bytes_t *in) +{ + int result; + const char *out; + unsigned outlen; + result = sasl_server_start(sasl->cyrus_conn, + mech_selected, + in->start, in->size, + &out, &outlen); + + sasl->cyrus_out.start = out; + sasl->cyrus_out.size = outlen; + return result; +} + +static void pni_process_server_result(pn_transport_t *transport, int result) +{ + pni_sasl_t *sasl = transport->sasl; + switch (result) { + case SASL_OK: + // Authenticated + sasl->outcome = PN_SASL_OK; + transport->authenticated = true; + // Get username from SASL + const void* value; + sasl_getprop(sasl->cyrus_conn, SASL_USERNAME, &value); + sasl->username = (const char*) value; + pn_transport_logf(transport, "Authenticated user: %s with mechanism %s", sasl->username, sasl->selected_mechanism); + pni_sasl_set_desired_state(transport, SASL_POSTED_OUTCOME); + break; + case SASL_CONTINUE: + // Need to send a challenge + pni_sasl_set_desired_state(transport, SASL_POSTED_CHALLENGE); + break; + default: + pni_check_sasl_result(sasl->cyrus_conn, result, transport); + + // Failed to authenticate + sasl->outcome = PN_SASL_AUTH; + pni_sasl_set_desired_state(transport, SASL_POSTED_OUTCOME); + break; + } + pni_emit(transport); +} + +static void pni_process_init(pn_transport_t *transport, const char *mechanism, const pn_bytes_t *recv) +{ + pni_sasl_t *sasl = transport->sasl; + + int result = pni_wrap_server_start(sasl, mechanism, recv); + if (result==SASL_OK) { + // We need to filter out a supplied mech in in the inclusion list + // as the client could have used a mech that we support, but that + // wasn't on the list we sent. + if (!pni_included_mech(sasl->included_mechanisms, pn_bytes(strlen(mechanism), mechanism))) { + sasl_seterror(sasl->cyrus_conn, 0, "Client mechanism not in mechanism inclusion list."); + result = SASL_FAIL; + } + } + pni_process_server_result(transport, result); +} + +static int pni_wrap_server_step(pni_sasl_t *sasl, const pn_bytes_t *in) +{ + int result; + const char *out; + unsigned outlen; + result = sasl_server_step(sasl->cyrus_conn, + in->start, in->size, + &out, &outlen); + + sasl->cyrus_out.start = out; + sasl->cyrus_out.size = outlen; + return result; +} + +static void pni_process_response(pn_transport_t *transport, const pn_bytes_t *recv) +{ + pni_sasl_t *sasl = transport->sasl; + int result = pni_wrap_server_step(sasl, recv); + pni_process_server_result(transport, result); +} + +pn_sasl_t *pn_sasl(pn_transport_t *transport) +{ + if (!transport->sasl) { + pni_sasl_t *sasl = (pni_sasl_t *) malloc(sizeof(pni_sasl_t)); + + const char *sasl_config_path = getenv("PN_SASL_CONFIG_PATH"); + + sasl->client = !transport->server; + sasl->selected_mechanism = NULL; + sasl->included_mechanisms = NULL; + sasl->username = NULL; + sasl->password = NULL; + sasl->config_name = sasl->client ? "proton-client" : "proton-server"; + sasl->config_dir = sasl_config_path ? strdup(sasl_config_path) : NULL; + sasl->remote_fqdn = NULL; + sasl->outcome = PN_SASL_NONE; + sasl->cyrus_conn = NULL; + sasl->cyrus_out.size = 0; + sasl->cyrus_out.start = NULL; + sasl->desired_state = SASL_NONE; + sasl->last_state = SASL_NONE; + sasl->halt = false; + + transport->sasl = sasl; + } + + // The actual external pn_sasl_t pointer is a pointer to its enclosing pn_transport_t + return (pn_sasl_t *)transport; +} + +// This is a hack to tell us that +// no actual negotiation is going to happen and we can go +// straight to the AMQP layer; it can only work on the client side +// As the server doesn't know if SASL is even active until it sees +// the SASL header from the client first. +static void pni_sasl_force_anonymous(pn_transport_t *transport) +{ + pni_sasl_t *sasl = transport->sasl; + if (sasl->client) { + // Pretend we got sasl mechanisms frame with just ANONYMOUS + pni_process_mechanisms(transport, "ANONYMOUS", true); + } + pni_emit(transport); +} + +void pni_sasl_set_remote_hostname(pn_transport_t * transport, const char * fqdn) +{ + pni_sasl_t *sasl = transport->sasl; + sasl->remote_fqdn = fqdn; +} + +void pni_sasl_set_user_password(pn_transport_t *transport, const char *user, const char *password) +{ + pni_sasl_t *sasl = transport->sasl; + sasl->username = user; + free(sasl->password); + sasl->password = password ? strdup(password) : NULL; +} + +const char *pn_sasl_get_user(pn_sasl_t *sasl0) +{ + pni_sasl_t *sasl = get_sasl_internal(sasl0); + return sasl->username; +} + +const char *pn_sasl_get_mech(pn_sasl_t *sasl0) +{ + pni_sasl_t *sasl = get_sasl_internal(sasl0); + return sasl->selected_mechanism; +} + +void pn_sasl_allowed_mechs(pn_sasl_t *sasl0, const char *mechs) +{ + pni_sasl_t *sasl = get_sasl_internal(sasl0); + free(sasl->included_mechanisms); + sasl->included_mechanisms = mechs ? strdup(mechs) : NULL; + if (strcmp(mechs, "ANONYMOUS")==0 ) { + pn_transport_t *transport = get_transport_internal(sasl0); + pni_sasl_force_anonymous(transport); + } +} + +void pn_sasl_config_name(pn_sasl_t *sasl0, const char *name) +{ + pni_sasl_t *sasl = get_sasl_internal(sasl0); + sasl->config_name = name; +} + +void pn_sasl_config_path(pn_sasl_t *sasl0, const char *dir) +{ + pni_sasl_t *sasl = get_sasl_internal(sasl0); + free(sasl->config_dir); + sasl->config_dir = strdup(dir); +} + +void pn_sasl_done(pn_sasl_t *sasl0, pn_sasl_outcome_t outcome) +{ + pni_sasl_t *sasl = get_sasl_internal(sasl0); + if (sasl) { + sasl->outcome = outcome; + } +} + +pn_sasl_outcome_t pn_sasl_outcome(pn_sasl_t *sasl0) +{ + pni_sasl_t *sasl = get_sasl_internal(sasl0); + return sasl ? sasl->outcome : PN_SASL_NONE; +} + +void pn_sasl_free(pn_transport_t *transport) +{ + if (transport) { + pni_sasl_t *sasl = transport->sasl; + if (sasl) { + free(sasl->selected_mechanism); + free(sasl->included_mechanisms); + free(sasl->password); + free(sasl->config_dir); + + // CYRUS_SASL + if (sasl->cyrus_conn) { + sasl_dispose(&sasl->cyrus_conn); + if (sasl->client) { + sasl_client_done(); + } else { + sasl_server_done(); + } + } + + free(sasl); + } + } +} + +static void pni_sasl_server_init(pn_transport_t *transport) +{ + int r = pni_wrap_server_new(transport); + + if (!pni_check_sasl_result(transport->sasl->cyrus_conn, r, transport)) { + return; + } + + // Setup to send SASL mechanisms frame + pni_sasl_set_desired_state(transport, SASL_POSTED_MECHANISMS); +} + +static void pn_sasl_process(pn_transport_t *transport) +{ + pni_sasl_t *sasl = transport->sasl; + if (!sasl->client) { + if (sasl->desired_state<SASL_POSTED_MECHANISMS) { + pni_sasl_server_init(transport); + } + } +} + +ssize_t pn_sasl_input(pn_transport_t *transport, const char *bytes, size_t available) +{ + pn_sasl_process(transport); + + pni_sasl_t *sasl = transport->sasl; + ssize_t n = pn_dispatcher_input(transport, bytes, available, false, &sasl->halt); + + if (n==0 && pni_sasl_is_final_input_state(sasl)) { + return PN_EOS; + } + return n; +} + +ssize_t pn_sasl_output(pn_transport_t *transport, char *bytes, size_t size) +{ + pn_sasl_process(transport); + + pni_post_sasl_frame(transport); + + pni_sasl_t *sasl = transport->sasl; + if (transport->available == 0 && pni_sasl_is_final_output_state(sasl)) { + if (sasl->outcome != PN_SASL_OK && pni_sasl_is_final_input_state(sasl)) { + pn_transport_close_tail(transport); + } + return PN_EOS; + } else { + return pn_dispatcher_output(transport, bytes, size); + } +} + +// Received Server side +int pn_do_init(pn_transport_t *transport, uint8_t frame_type, uint16_t channel, pn_data_t *args, const pn_bytes_t *payload) +{ + pni_sasl_t *sasl = transport->sasl; + pn_bytes_t mech; + pn_bytes_t recv; + int err = pn_data_scan(args, "D.[sz]", &mech, &recv); + if (err) return err; + sasl->selected_mechanism = pn_strndup(mech.start, mech.size); + + pni_process_init(transport, sasl->selected_mechanism, &recv); + + return 0; +} + +// Received client side +int pn_do_mechanisms(pn_transport_t *transport, uint8_t frame_type, uint16_t channel, pn_data_t *args, const pn_bytes_t *payload) +{ + // If we already pretended we got the ANONYMOUS mech then ignore + if (transport->sasl->last_state==SASL_PRETEND_OUTCOME) return 0; + + pn_string_t *mechs = pn_string(""); + + // This scanning relies on pn_data_scan leaving the pn_data_t cursors + // where they are after finishing the scan + int err = pn_data_scan(args, "D.[@["); + if (err) return err; + + // Now keep checking for end of array and pull a symbol + while(pn_data_next(args)) { + pn_bytes_t s = pn_data_get_symbol(args); + if (pni_included_mech(transport->sasl->included_mechanisms, s)) { + pn_string_addf(mechs, "%*s ", (int)s.size, s.start); + } + } + pn_string_buffer(mechs)[pn_string_size(mechs)-1] = 0; + + pni_process_mechanisms(transport, pn_string_get(mechs), false); + + pn_free(mechs); + return 0; +} + +// Received client side +int pn_do_challenge(pn_transport_t *transport, uint8_t frame_type, uint16_t channel, pn_data_t *args, const pn_bytes_t *payload) +{ + pn_bytes_t recv; + int err = pn_data_scan(args, "D.[z]", &recv); + if (err) return err; + + pni_process_challenge(transport, &recv); + + return 0; +} + +// Received server side +int pn_do_response(pn_transport_t *transport, uint8_t frame_type, uint16_t channel, pn_data_t *args, const pn_bytes_t *payload) +{ + pn_bytes_t recv; + int err = pn_data_scan(args, "D.[z]", &recv); + if (err) return err; + + pni_process_response(transport, &recv); + + return 0; +} + +// Received client side +int pn_do_outcome(pn_transport_t *transport, uint8_t frame_type, uint16_t channel, pn_data_t *args, const pn_bytes_t *payload) +{ + pni_sasl_t *sasl = transport->sasl; + uint8_t outcome; + int err = pn_data_scan(args, "D.[B]", &outcome); + if (err) return err; + sasl->outcome = (pn_sasl_outcome_t) outcome; + sasl->last_state = SASL_RECVED_OUTCOME; + sasl->halt = true; + pni_emit(transport); + return 0; +} http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4a09c6a1/proton-c/src/sasl/none_sasl.c ---------------------------------------------------------------------- diff --git a/proton-c/src/sasl/none_sasl.c b/proton-c/src/sasl/none_sasl.c new file mode 100644 index 0000000..87f7917 --- /dev/null +++ b/proton-c/src/sasl/none_sasl.c @@ -0,0 +1,425 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include <assert.h> +#include <proton/codec.h> + +#include "buffer.h" +#include "protocol.h" +#include "dispatch_actions.h" +#include "engine/engine-internal.h" + +struct pni_sasl_t { + char *selected_mechanism; + char *included_mechanisms; + const char *username; + char *password; + const char *remote_fqdn; + pn_buffer_t *send_data; + pn_buffer_t *recv_data; + pn_sasl_outcome_t outcome; + bool client; + bool sent_init; + bool rcvd_init; + bool sent_done; + bool rcvd_done; + bool halt; +}; + +static inline pn_transport_t *get_transport_internal(pn_sasl_t *sasl) +{ + // The external pn_sasl_t is really a pointer to the internal pni_transport_t + return ((pn_transport_t *)sasl); +} + +static inline pni_sasl_t *get_sasl_internal(pn_sasl_t *sasl) +{ + // The external pn_sasl_t is really a pointer to the internal pni_transport_t + return sasl ? ((pn_transport_t *)sasl)->sasl : NULL; +} + +static void pni_emit(pn_sasl_t *sasl) { + pn_transport_t *transport = get_transport_internal(sasl); + if (transport->connection && transport->connection->collector) { + pn_collector_t *collector = transport->connection->collector; + pn_collector_put(collector, PN_OBJECT, transport, PN_TRANSPORT); + } +} + +pn_sasl_t *pn_sasl(pn_transport_t *transport) +{ + if (!transport->sasl) { + pni_sasl_t *sasl = (pni_sasl_t *) malloc(sizeof(pni_sasl_t)); + + sasl->client = !transport->server; + sasl->included_mechanisms = NULL; + sasl->selected_mechanism = NULL; + sasl->send_data = pn_buffer(16); + sasl->recv_data = pn_buffer(16); + sasl->outcome = PN_SASL_NONE; + sasl->sent_init = false; + sasl->rcvd_init = false; + sasl->sent_done = false; + sasl->rcvd_done = false; + sasl->halt = false; + + transport->sasl = sasl; + } + + // The actual external pn_sasl_t pointer is a pointer to its enclosing pn_transport_t + return (pn_sasl_t *)transport; +} + +void pn_sasl_allowed_mechs(pn_sasl_t *sasl0, const char *mechs) +{ + pni_sasl_t *sasl = get_sasl_internal(sasl0); + if (!sasl) return; + free(sasl->included_mechanisms); + sasl->included_mechanisms = mechs ? strdup(mechs) : NULL; + if (strcmp(mechs, "ANONYMOUS")==0 ) { + // If we do this on the client it is a hack to tell us that + // no actual negatiation is going to happen and we can go + // straight to the AMQP layer + if (sasl->client) { + sasl->rcvd_done = true; + sasl->sent_done = true; + } + } + pni_emit(sasl0); +} + +ssize_t pn_sasl_send(pn_sasl_t *sasl0, const char *bytes, size_t size) +{ + pni_sasl_t *sasl = get_sasl_internal(sasl0); + if (sasl) { + if (pn_buffer_size(sasl->send_data)) { + // XXX: need better error + return PN_STATE_ERR; + } + int err = pn_buffer_append(sasl->send_data, bytes, size); + if (err) return err; + pni_emit(sasl0); + return size; + } else { + return PN_ARG_ERR; + } +} + +size_t pn_sasl_pending(pn_sasl_t *sasl0) +{ + pni_sasl_t *sasl = get_sasl_internal(sasl0); + if (sasl && pn_buffer_size(sasl->recv_data)) { + return pn_buffer_size(sasl->recv_data); + } else { + return 0; + } +} + +ssize_t pn_sasl_recv(pn_sasl_t *sasl0, char *bytes, size_t size) +{ + pni_sasl_t *sasl = get_sasl_internal(sasl0); + if (!sasl) return PN_ARG_ERR; + + size_t bsize = pn_buffer_size(sasl->recv_data); + if (bsize) { + if (bsize > size) return PN_OVERFLOW; + pn_buffer_get(sasl->recv_data, 0, bsize, bytes); + pn_buffer_clear(sasl->recv_data); + return bsize; + } else { + return PN_EOS; + } +} + +void pn_sasl_client(pn_sasl_t *sasl) +{ +} + +void pn_sasl_server(pn_sasl_t *sasl0) +{ + pni_sasl_t *sasl = get_sasl_internal(sasl0); + if (sasl) { + sasl->client = false; + } +} + +void pni_sasl_set_user_password(pn_transport_t *transport, const char *user, const char *password) +{ + pni_sasl_t *sasl = transport->sasl; + sasl->username = user; + sasl->password = password ? strdup(password) : NULL; +} + +const char *pn_sasl_get_user(pn_sasl_t *sasl0) +{ + return "anonymous"; +} + +const char *pn_sasl_get_mech(pn_sasl_t *sasl0) +{ + return "ANONYMOUS"; +} + +void pn_sasl_config_name(pn_sasl_t *sasl0, const char *name) +{ +} + +void pn_sasl_config_path(pn_sasl_t *sasl0, const char *path) +{ +} + +void pni_sasl_set_remote_hostname(pn_transport_t * transport, const char * fqdn) +{ + pni_sasl_t *sasl = transport->sasl; + sasl->remote_fqdn = fqdn; +} + +void pn_sasl_plain(pn_sasl_t *sasl0, const char *username, const char *password) +{ + pni_sasl_t *sasl = get_sasl_internal(sasl0); + if (!sasl) return; + + const char *user = username ? username : ""; + const char *pass = password ? password : ""; + size_t usize = strlen(user); + size_t psize = strlen(pass); + size_t size = usize + psize + 2; + char *iresp = (char *) malloc(size); + + iresp[0] = 0; + memmove(iresp + 1, user, usize); + iresp[usize + 1] = 0; + memmove(iresp + usize + 2, pass, psize); + + pn_sasl_allowed_mechs(sasl0, "PLAIN"); + pn_sasl_send(sasl0, iresp, size); + free(iresp); +} + +void pn_sasl_done(pn_sasl_t *sasl0, pn_sasl_outcome_t outcome) +{ + pni_sasl_t *sasl = get_sasl_internal(sasl0); + if (sasl) { + sasl->outcome = outcome; + pni_emit(sasl0); + } +} + +pn_sasl_outcome_t pn_sasl_outcome(pn_sasl_t *sasl0) +{ + pni_sasl_t *sasl = get_sasl_internal(sasl0); + return sasl ? sasl->outcome : PN_SASL_NONE; +} + +void pn_sasl_free(pn_transport_t *transport) +{ + if (transport) { + pni_sasl_t *sasl = transport->sasl; + if (sasl) { + free(sasl->included_mechanisms); + free(sasl->selected_mechanism); + pn_buffer_free(sasl->send_data); + pn_buffer_free(sasl->recv_data); + free(sasl); + } + } +} + +void pn_client_init(pn_transport_t *transport) +{ + pni_sasl_t *sasl = transport->sasl; + pn_buffer_memory_t bytes = pn_buffer_memory(sasl->send_data); + pn_post_frame(transport, SASL_FRAME_TYPE, 0, "DL[sz]", SASL_INIT, sasl->included_mechanisms, + bytes.size, bytes.start); + pn_buffer_clear(sasl->send_data); + pni_emit((pn_sasl_t *) transport); +} + +void pni_sasl_server_init(pn_transport_t *transport) +{ + pni_sasl_t *sasl = transport->sasl; + // XXX + char *mechs[16]; + int count = 0; + + if (sasl->included_mechanisms) { + char *start = sasl->included_mechanisms; + char *end = start; + + while (*end) { + if (*end == ' ') { + if (start != end) { + *end = '\0'; + mechs[count++] = start; + } + end++; + start = end; + } else { + end++; + } + } + + if (start != end) { + mechs[count++] = start; + } + } + + pn_post_frame(transport, SASL_FRAME_TYPE, 0, "DL[@T[*s]]", SASL_MECHANISMS, PN_SYMBOL, count, mechs); + pni_emit((pn_sasl_t *) transport); +} + +void pn_server_done(pn_sasl_t *sasl0) +{ + pn_transport_t *transport = get_transport_internal(sasl0); + pni_sasl_t *sasl = transport->sasl; + pn_post_frame(transport, SASL_FRAME_TYPE, 0, "DL[B]", SASL_OUTCOME, sasl->outcome); + pni_emit(sasl0); +} + +void pn_sasl_process(pn_transport_t *transport) +{ + pni_sasl_t *sasl = transport->sasl; + if (!sasl->sent_init) { + if (sasl->client) { + pn_client_init(transport); + } else { + pni_sasl_server_init(transport); + } + sasl->sent_init = true; + } + + if (pn_buffer_size(sasl->send_data)) { + pn_buffer_memory_t bytes = pn_buffer_memory(sasl->send_data); + pn_post_frame(transport, SASL_FRAME_TYPE, 0, "DL[z]", sasl->client ? SASL_RESPONSE : SASL_CHALLENGE, + bytes.size, bytes.start); + pn_buffer_clear(sasl->send_data); + pni_emit((pn_sasl_t *) transport); + } + + if (!sasl->client && sasl->outcome != PN_SASL_NONE && !sasl->sent_done) { + pn_server_done((pn_sasl_t *)transport); + sasl->sent_done = true; + } + + // XXX: need to finish this check when challenge/response is complete + // check for client is outome is received + // check for server is that there are no pending frames (either init + // or challenges) from client + if (!sasl->client && sasl->sent_done && sasl->rcvd_init) { + sasl->rcvd_done = true; + sasl->halt = true; + } +} + +ssize_t pn_sasl_input(pn_transport_t *transport, const char *bytes, size_t available) +{ + pni_sasl_t *sasl = transport->sasl; + ssize_t n = pn_dispatcher_input(transport, bytes, available, false, &sasl->halt); + if (n < 0) return n; + + pn_sasl_process(transport); + + if (sasl->rcvd_done) { + if (sasl->outcome == PN_SASL_OK) { + if (n) { + return n; + } else { + return PN_EOS; + } + } else { + // XXX: should probably do something better here + return PN_EOS; + } + } else { + return n; + } +} + +ssize_t pn_sasl_output(pn_transport_t *transport, char *bytes, size_t size) +{ + pn_sasl_process(transport); + + pni_sasl_t *sasl = transport->sasl; + if (transport->available == 0 && sasl->sent_done) { + if (sasl->outcome == PN_SASL_OK) { + return PN_EOS; + } else { + // XXX: should probably do something better here + return PN_EOS; + } + } else { + return pn_dispatcher_output(transport, bytes, size); + } +} + +int pn_do_init(pn_transport_t *transport, uint8_t frame_type, uint16_t channel, pn_data_t *args, const pn_bytes_t *payload) +{ + pni_sasl_t *sasl = transport->sasl; + pn_bytes_t mech; + pn_bytes_t recv; + int err = pn_data_scan(args, "D.[sz]", &mech, &recv); + if (err) return err; + sasl->selected_mechanism = pn_strndup(mech.start, mech.size); + pn_buffer_append(sasl->recv_data, recv.start, recv.size); + sasl->rcvd_init = true; + return 0; +} + +int pn_do_mechanisms(pn_transport_t *transport, uint8_t frame_type, uint16_t channel, pn_data_t *args, const pn_bytes_t *payload) +{ + pni_sasl_t *sasl = transport->sasl; + sasl->rcvd_init = true; + return 0; +} + +int pn_do_recv(pn_transport_t *transport, uint8_t frame_type, uint16_t channel, pn_data_t *args, const pn_bytes_t *payload) +{ + pni_sasl_t *sasl = transport->sasl; + pn_bytes_t recv; + int err = pn_data_scan(args, "D.[z]", &recv); + if (err) return err; + pn_buffer_append(sasl->recv_data, recv.start, recv.size); + return 0; +} + +int pn_do_challenge(pn_transport_t *transport, uint8_t frame_type, uint16_t channel, pn_data_t *args, const pn_bytes_t *payload) +{ + return pn_do_recv(transport, frame_type, channel, args, payload); +} + +int pn_do_response(pn_transport_t *transport, uint8_t frame_type, uint16_t channel, pn_data_t *args, const pn_bytes_t *payload) +{ + return pn_do_recv(transport, frame_type, channel, args, payload); +} + +int pn_do_outcome(pn_transport_t *transport, uint8_t frame_type, uint16_t channel, pn_data_t *args, const pn_bytes_t *payload) +{ + pni_sasl_t *sasl = transport->sasl; + uint8_t outcome; + int err = pn_data_scan(args, "D.[B]", &outcome); + if (err) return err; + sasl->outcome = (pn_sasl_outcome_t) outcome; + sasl->rcvd_done = true; + sasl->sent_done = true; + sasl->halt = true; + pni_emit((pn_sasl_t *) transport); + return 0; +} http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4a09c6a1/proton-c/src/sasl/sasl-internal.h ---------------------------------------------------------------------- diff --git a/proton-c/src/sasl/sasl-internal.h b/proton-c/src/sasl/sasl-internal.h index a8390df..965eff2 100644 --- a/proton-c/src/sasl/sasl-internal.h +++ b/proton-c/src/sasl/sasl-internal.h @@ -22,7 +22,7 @@ #ifndef PROTON_SASL_INTERNAL_H #define PROTON_SASL_INTERNAL_H 1 -#include <proton/sasl.h> +#include "proton/types.h" /** Destructor for the given SASL layer. * @@ -31,6 +31,10 @@ */ void pn_sasl_free(pn_transport_t *transport); -bool pn_sasl_skipping_allowed(pn_transport_t *transport); +ssize_t pn_sasl_input(pn_transport_t *transport, const char *bytes, size_t available); +ssize_t pn_sasl_output(pn_transport_t *transport, char *bytes, size_t size); + +void pni_sasl_set_user_password(pn_transport_t *transport, const char *user, const char *password); +void pni_sasl_set_remote_hostname(pn_transport_t *transport, const char* fqdn); #endif /* sasl-internal.h */ http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4a09c6a1/proton-c/src/sasl/sasl.c ---------------------------------------------------------------------- diff --git a/proton-c/src/sasl/sasl.c b/proton-c/src/sasl/sasl.c index 191c23e..0c58b1f 100644 --- a/proton-c/src/sasl/sasl.c +++ b/proton-c/src/sasl/sasl.c @@ -19,39 +19,12 @@ * */ -#include <assert.h> -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <proton/error.h> -#include <proton/sasl.h> +#include "sasl-internal.h" -#include "buffer.h" -#include "protocol.h" -#include "dispatch_actions.h" -#include "framing/framing.h" #include "engine/engine-internal.h" -#include "dispatcher/dispatcher.h" -#include "util.h" #include "transport/autodetect.h" - -struct pni_sasl_t { - char *mechanisms; - char *remote_mechanisms; - pn_buffer_t *send_data; - pn_buffer_t *recv_data; - pn_sasl_outcome_t outcome; - bool client; - bool allow_skip; - bool sent_init; - bool rcvd_init; - bool sent_done; - bool rcvd_done; - bool halt; - bool input_bypass; - bool output_bypass; -}; +#include <assert.h> static inline pn_transport_t *get_transport_internal(pn_sasl_t *sasl) { @@ -59,10 +32,11 @@ static inline pn_transport_t *get_transport_internal(pn_sasl_t *sasl) return ((pn_transport_t *)sasl); } -static inline pni_sasl_t *get_sasl_internal(pn_sasl_t *sasl) +void pn_sasl_allow_skip(pn_sasl_t *sasl0, bool allow) { - // The external pn_sasl_t is really a pointer to the internal pni_transport_t - return sasl ? ((pn_transport_t *)sasl)->sasl : NULL; + if (!sasl0) return; + pn_transport_t *transport = get_transport_internal(sasl0); + pn_transport_require_auth(transport, !allow); } static ssize_t pn_input_read_sasl_header(pn_transport_t* transport, unsigned int layer, const char* bytes, size_t available); @@ -98,389 +72,6 @@ const pn_io_layer_t sasl_layer = { NULL }; -static void pni_emit(pn_sasl_t *sasl) { - pn_transport_t *transport = get_transport_internal(sasl); - if (transport->connection && transport->connection->collector) { - pn_collector_t *collector = transport->connection->collector; - pn_collector_put(collector, PN_OBJECT, transport, PN_TRANSPORT); - } -} - -pn_sasl_t *pn_sasl(pn_transport_t *transport) -{ - if (!transport->sasl) { - pni_sasl_t *sasl = (pni_sasl_t *) malloc(sizeof(pni_sasl_t)); - - sasl->client = !transport->server; - sasl->mechanisms = NULL; - sasl->remote_mechanisms = NULL; - sasl->send_data = pn_buffer(16); - sasl->recv_data = pn_buffer(16); - sasl->outcome = PN_SASL_NONE; - sasl->allow_skip = true; - sasl->sent_init = false; - sasl->rcvd_init = false; - sasl->sent_done = false; - sasl->rcvd_done = false; - sasl->input_bypass = false; - sasl->output_bypass = false; - sasl->halt = false; - - transport->sasl = sasl; - } - - // The actual external pn_sasl_t pointer is a pointer to its enclosing pn_transport_t - return (pn_sasl_t *)transport; -} - -pn_sasl_state_t pn_sasl_state(pn_sasl_t *sasl0) -{ - pni_sasl_t *sasl = get_sasl_internal(sasl0); - if (sasl) { - if (sasl->outcome == PN_SASL_NONE) { - return sasl->rcvd_init ? PN_SASL_STEP : PN_SASL_IDLE; - } else { - return sasl->outcome == PN_SASL_OK ? PN_SASL_PASS : PN_SASL_FAIL; - } - // if (sasl->rcvd_init && sasl->outcome == PN_SASL_NONE) return PN_SASL_STEP; - //if (sasl->outcome == PN_SASL_OK) return PN_SASL_PASS; - //else return PN_SASL_FAIL; - } else { - return PN_SASL_FAIL; - } -} - -void pn_sasl_mechanisms(pn_sasl_t *sasl0, const char *mechanisms) -{ - pni_sasl_t *sasl = get_sasl_internal(sasl0); - if (!sasl) return; - sasl->mechanisms = pn_strdup(mechanisms); - pni_emit(sasl0); -} - -const char *pn_sasl_remote_mechanisms(pn_sasl_t *sasl0) -{ - pni_sasl_t *sasl = get_sasl_internal(sasl0); - return sasl ? sasl->remote_mechanisms : NULL; -} - -ssize_t pn_sasl_send(pn_sasl_t *sasl0, const char *bytes, size_t size) -{ - pni_sasl_t *sasl = get_sasl_internal(sasl0); - if (sasl) { - if (pn_buffer_size(sasl->send_data)) { - // XXX: need better error - return PN_STATE_ERR; - } - int err = pn_buffer_append(sasl->send_data, bytes, size); - if (err) return err; - pni_emit(sasl0); - return size; - } else { - return PN_ARG_ERR; - } -} - -size_t pn_sasl_pending(pn_sasl_t *sasl0) -{ - pni_sasl_t *sasl = get_sasl_internal(sasl0); - if (sasl && pn_buffer_size(sasl->recv_data)) { - return pn_buffer_size(sasl->recv_data); - } else { - return 0; - } -} - -ssize_t pn_sasl_recv(pn_sasl_t *sasl0, char *bytes, size_t size) -{ - pni_sasl_t *sasl = get_sasl_internal(sasl0); - if (!sasl) return PN_ARG_ERR; - - size_t bsize = pn_buffer_size(sasl->recv_data); - if (bsize) { - if (bsize > size) return PN_OVERFLOW; - pn_buffer_get(sasl->recv_data, 0, bsize, bytes); - pn_buffer_clear(sasl->recv_data); - return bsize; - } else { - return PN_EOS; - } -} - -void pn_sasl_client(pn_sasl_t *sasl) -{ -} - -void pn_sasl_server(pn_sasl_t *sasl0) -{ - pni_sasl_t *sasl = get_sasl_internal(sasl0); - if (sasl) { - sasl->client = false; - } -} - -void pn_sasl_allow_skip(pn_sasl_t *sasl0, bool allow) -{ - pni_sasl_t *sasl = get_sasl_internal(sasl0); - if (sasl) - sasl->allow_skip = allow; -} - -bool pn_sasl_skipping_allowed(pn_transport_t *transport) -{ - return transport && ( !transport->sasl || transport->sasl->allow_skip ); -} - -void pn_sasl_plain(pn_sasl_t *sasl0, const char *username, const char *password) -{ - pni_sasl_t *sasl = get_sasl_internal(sasl0); - if (!sasl) return; - - const char *user = username ? username : ""; - const char *pass = password ? password : ""; - size_t usize = strlen(user); - size_t psize = strlen(pass); - size_t size = usize + psize + 2; - char *iresp = (char *) malloc(size); - - iresp[0] = 0; - memmove(iresp + 1, user, usize); - iresp[usize + 1] = 0; - memmove(iresp + usize + 2, pass, psize); - - pn_sasl_mechanisms(sasl0, "PLAIN"); - pn_sasl_send(sasl0, iresp, size); - free(iresp); -} - -void pn_sasl_done(pn_sasl_t *sasl0, pn_sasl_outcome_t outcome) -{ - pni_sasl_t *sasl = get_sasl_internal(sasl0); - if (sasl) { - sasl->outcome = outcome; - // If we do this on the client it is a hack to tell us that - // no actual negatiation is going to happen and we can go - // straight to the AMQP layer - if (sasl->client) { - sasl->rcvd_done = true; - sasl->sent_done = true; - } - pni_emit(sasl0); - } -} - -pn_sasl_outcome_t pn_sasl_outcome(pn_sasl_t *sasl0) -{ - pni_sasl_t *sasl = get_sasl_internal(sasl0); - return sasl ? sasl->outcome : PN_SASL_NONE; -} - -void pn_sasl_free(pn_transport_t *transport) -{ - if (transport) { - pni_sasl_t *sasl = transport->sasl; - if (sasl) { - free(sasl->mechanisms); - free(sasl->remote_mechanisms); - pn_buffer_free(sasl->send_data); - pn_buffer_free(sasl->recv_data); - free(sasl); - } - } -} - -void pn_client_init(pn_transport_t *transport) -{ - pni_sasl_t *sasl = transport->sasl; - pn_buffer_memory_t bytes = pn_buffer_memory(sasl->send_data); - pn_post_frame(transport, SASL_FRAME_TYPE, 0, "DL[sz]", SASL_INIT, sasl->mechanisms, - bytes.size, bytes.start); - pn_buffer_clear(sasl->send_data); - pni_emit((pn_sasl_t *) transport); -} - -void pn_server_init(pn_transport_t *transport) -{ - pni_sasl_t *sasl = transport->sasl; - // XXX - char *mechs[16]; - int count = 0; - - if (sasl->mechanisms) { - char *start = sasl->mechanisms; - char *end = start; - - while (*end) { - if (*end == ' ') { - if (start != end) { - *end = '\0'; - mechs[count++] = start; - } - end++; - start = end; - } else { - end++; - } - } - - if (start != end) { - mechs[count++] = start; - } - } - - pn_post_frame(transport, SASL_FRAME_TYPE, 0, "DL[@T[*s]]", SASL_MECHANISMS, PN_SYMBOL, count, mechs); - pni_emit((pn_sasl_t *) transport); -} - -void pn_server_done(pn_sasl_t *sasl0) -{ - pn_transport_t *transport = get_transport_internal(sasl0); - pni_sasl_t *sasl = transport->sasl; - pn_post_frame(transport, SASL_FRAME_TYPE, 0, "DL[B]", SASL_OUTCOME, sasl->outcome); - pni_emit(sasl0); -} - -void pn_sasl_process(pn_transport_t *transport) -{ - pni_sasl_t *sasl = transport->sasl; - if (!sasl->sent_init) { - if (sasl->client) { - pn_client_init(transport); - } else { - pn_server_init(transport); - } - sasl->sent_init = true; - } - - if (pn_buffer_size(sasl->send_data)) { - pn_buffer_memory_t bytes = pn_buffer_memory(sasl->send_data); - pn_post_frame(transport, SASL_FRAME_TYPE, 0, "DL[z]", sasl->client ? SASL_RESPONSE : SASL_CHALLENGE, - bytes.size, bytes.start); - pn_buffer_clear(sasl->send_data); - pni_emit((pn_sasl_t *) transport); - } - - if (!sasl->client && sasl->outcome != PN_SASL_NONE && !sasl->sent_done) { - pn_server_done((pn_sasl_t *)transport); - sasl->sent_done = true; - } - - // XXX: need to finish this check when challenge/response is complete - // check for client is outome is received - // check for server is that there are no pending frames (either init - // or challenges) from client - if (!sasl->client && sasl->sent_done && sasl->rcvd_init) { - sasl->rcvd_done = true; - sasl->halt = true; - } -} - -ssize_t pn_sasl_input(pn_transport_t *transport, const char *bytes, size_t available) -{ - pni_sasl_t *sasl = transport->sasl; - ssize_t n = pn_dispatcher_input(transport, bytes, available, false, &sasl->halt); - if (n < 0) return n; - - pn_sasl_process(transport); - - if (sasl->rcvd_done) { - if (pn_sasl_state((pn_sasl_t *)transport) == PN_SASL_PASS) { - if (n) { - return n; - } else { - return PN_EOS; - } - } else { - // XXX: should probably do something better here - return PN_ERR; - } - } else { - return n; - } -} - -ssize_t pn_sasl_output(pn_transport_t *transport, char *bytes, size_t size) -{ - pn_sasl_process(transport); - - pni_sasl_t *sasl = transport->sasl; - if (transport->available == 0 && sasl->sent_done) { - if (pn_sasl_state((pn_sasl_t *)transport) == PN_SASL_PASS) { - return PN_EOS; - } else { - // XXX: should probably do something better here - return PN_ERR; - } - } else { - return pn_dispatcher_output(transport, bytes, size); - } -} - -int pn_do_init(pn_transport_t *transport, uint8_t frame_type, uint16_t channel, pn_data_t *args, const pn_bytes_t *payload) -{ - pni_sasl_t *sasl = transport->sasl; - assert(sasl && !sasl->client); - pn_bytes_t mech; - pn_bytes_t recv; - int err = pn_data_scan(args, "D.[sz]", &mech, &recv); - if (err) return err; - sasl->remote_mechanisms = pn_strndup(mech.start, mech.size); - pn_buffer_append(sasl->recv_data, recv.start, recv.size); - sasl->rcvd_init = true; - return 0; -} - -int pn_do_mechanisms(pn_transport_t *transport, uint8_t frame_type, uint16_t channel, pn_data_t *args, const pn_bytes_t *payload) -{ - pni_sasl_t *sasl = transport->sasl; - assert(sasl && sasl->client); - sasl->rcvd_init = true; - return 0; -} - -int pn_do_recv(pn_transport_t *transport, uint8_t frame_type, uint16_t channel, pn_data_t *args, const pn_bytes_t *payload) -{ - pni_sasl_t *sasl = transport->sasl; - pn_bytes_t recv; - int err = pn_data_scan(args, "D.[z]", &recv); - if (err) return err; - pn_buffer_append(sasl->recv_data, recv.start, recv.size); - return 0; -} - -int pn_do_challenge(pn_transport_t *transport, uint8_t frame_type, uint16_t channel, pn_data_t *args, const pn_bytes_t *payload) -{ -#ifndef NDEBUG /* Avoid unused variable warnings in release builds. */ - pni_sasl_t *sasl = transport->sasl; - assert(sasl && sasl->client); -#endif - return pn_do_recv(transport, frame_type, channel, args, payload); -} - -int pn_do_response(pn_transport_t *transport, uint8_t frame_type, uint16_t channel, pn_data_t *args, const pn_bytes_t *payload) -{ -#ifndef NDEBUG /* Avoid unused variable warnings in release builds. */ - pni_sasl_t *sasl = transport->sasl; - assert(sasl && !sasl->client); -#endif - return pn_do_recv(transport, frame_type, channel, args, payload); -} - -int pn_do_outcome(pn_transport_t *transport, uint8_t frame_type, uint16_t channel, pn_data_t *args, const pn_bytes_t *payload) -{ - pni_sasl_t *sasl = transport->sasl; - assert(sasl && sasl->client); - uint8_t outcome; - int err = pn_data_scan(args, "D.[B]", &outcome); - if (err) return err; - sasl->outcome = (pn_sasl_outcome_t) outcome; - sasl->rcvd_done = true; - sasl->sent_done = true; - sasl->halt = true; - pni_emit((pn_sasl_t *) transport); - return 0; -} - #define SASL_HEADER ("AMQP\x03\x01\x00\x00") #define SASL_HEADER_LEN 8 @@ -504,23 +95,32 @@ static ssize_t pn_input_read_sasl_header(pn_transport_t* transport, unsigned int default: break; } + transport->close_sent = true; char quoted[1024]; pn_quote_data(quoted, 1024, bytes, available); pn_do_error(transport, "amqp:connection:framing-error", "%s header mismatch: %s ['%s']%s", "SASL", pni_protocol_name(protocol), quoted, !eos ? "" : " (connection aborted)"); + pn_set_error_layer(transport); return PN_EOS; } static ssize_t pn_input_read_sasl(pn_transport_t* transport, unsigned int layer, const char* bytes, size_t available) { - pni_sasl_t *sasl = transport->sasl; - if (!sasl->input_bypass) { + bool eos = pn_transport_capacity(transport)==PN_EOS; + if (eos) { + transport->close_sent = true; + pn_do_error(transport, "amqp:connection:framing-error", "connection aborted"); + pn_set_error_layer(transport); + return PN_EOS; + } + + if (!transport->sasl_input_bypass) { ssize_t n = pn_sasl_input(transport, bytes, available); if (n != PN_EOS) return n; - sasl->input_bypass = true; - if (sasl->output_bypass) + transport->sasl_input_bypass = true; + if (transport->sasl_output_bypass) transport->io_layers[layer] = &pni_passthru_layer; } return pni_passthru_layer.process_input(transport, layer, bytes, available ); @@ -542,8 +142,7 @@ static ssize_t pn_output_write_sasl_header(pn_transport_t *transport, unsigned i static ssize_t pn_output_write_sasl(pn_transport_t* transport, unsigned int layer, char* bytes, size_t available) { - pni_sasl_t *sasl = transport->sasl; - if (!sasl->output_bypass) { + if (!transport->sasl_output_bypass) { // this accounts for when pn_do_error is invoked, e.g. by idle timeout ssize_t n; if (transport->close_sent) { @@ -553,8 +152,8 @@ static ssize_t pn_output_write_sasl(pn_transport_t* transport, unsigned int laye } if (n != PN_EOS) return n; - sasl->output_bypass = true; - if (sasl->input_bypass) + transport->sasl_output_bypass = true; + if (transport->sasl_input_bypass) transport->io_layers[layer] = &pni_passthru_layer; } return pni_passthru_layer.process_output(transport, layer, bytes, available ); http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4a09c6a1/proton-c/src/ssl/openssl.c ---------------------------------------------------------------------- diff --git a/proton-c/src/ssl/openssl.c b/proton-c/src/ssl/openssl.c index b9331d5..42bbd71 100644 --- a/proton-c/src/ssl/openssl.c +++ b/proton-c/src/ssl/openssl.c @@ -807,6 +807,13 @@ pn_ssl_t *pn_ssl(pn_transport_t *transport) transport->ssl = ssl; + // Set up hostname from any bound connection + if (transport->connection) { + if (pn_string_size(transport->connection->hostname)) { + pn_ssl_set_peer_hostname((pn_ssl_t *) transport, pn_string_get(transport->connection->hostname)); + } + } + return (pn_ssl_t *) transport; } http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4a09c6a1/proton-c/src/tests/reactor.c ---------------------------------------------------------------------- diff --git a/proton-c/src/tests/reactor.c b/proton-c/src/tests/reactor.c index 8896c2c..fe6c769 100644 --- a/proton-c/src/tests/reactor.c +++ b/proton-c/src/tests/reactor.c @@ -288,7 +288,7 @@ static void test_reactor_connect(void) { pn_reactor_connection(reactor, ch); pn_reactor_run(reactor); expect(srv->events, PN_CONNECTION_INIT, PN_CONNECTION_BOUND, - PN_TRANSPORT, PN_CONNECTION_REMOTE_OPEN, + PN_CONNECTION_REMOTE_OPEN, PN_CONNECTION_LOCAL_OPEN, PN_TRANSPORT, PN_CONNECTION_REMOTE_CLOSE, PN_TRANSPORT_TAIL_CLOSED, PN_CONNECTION_LOCAL_CLOSE, PN_TRANSPORT, @@ -297,7 +297,7 @@ static void test_reactor_connect(void) { pn_free(srv->events); pn_decref(sh); expect(cli->events, PN_CONNECTION_INIT, PN_CONNECTION_LOCAL_OPEN, - PN_CONNECTION_BOUND, PN_TRANSPORT, PN_TRANSPORT, + PN_CONNECTION_BOUND, PN_CONNECTION_REMOTE_OPEN, PN_CONNECTION_LOCAL_CLOSE, PN_TRANSPORT, PN_TRANSPORT_HEAD_CLOSED, PN_CONNECTION_REMOTE_CLOSE, PN_TRANSPORT_TAIL_CLOSED, --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
