From ab5b764f39f4cc4dbc830c8d786478cff58a82bb Mon Sep 17 00:00:00 2001
From: Damien Diederen
Date: Wed, 30 Oct 2019 14:53:18 +0100
Subject: [PATCH] ZOOKEEPER-1112: Add (Cyrus) SASL authentication support to C
client library
This changeset allows C clients to use SASL to authenticate with the
ZooKeeper server. It is loosely based on patches #1 and #2 by Tom
Klonikowski, at https://reviews.apache.org/r/2252/, but the result has
been extensively reworked to follow the semantics of the Java client:
* No SASL operations are exposed through the API;
* The configuration is provided, and stored, at "handle init time";
* SASL authentication is automatically performed after each
(re)connect.
It introduces an optional dependency on the Cyrus SASL library, which
can either be autodetected (default) or configured using the
--without-sasl/--with-sasl[=DIR] flags.
TestServerRequireClientSASLAuth.cc has been renamed to TestSASLAuth.cc,
and a test has been added which successfully (re)authenticates using
the DIGEST-MD5 mechanism. An earlier version of this code has been
used to successfully authenticate clients via Kerberos.
While cli.c is not modified by this commit, we are planning to submit
a subsequent contribution which enables SASL support in that client
using the ZOOKEEPER-3599 (use getopt if available) mechanism.
Co-authored-by: Tom Klonikowski
---
tools/cmake/Modules/FindCyrusSASL.cmake | 54 ++
.../zookeeper-client-c/CMakeLists.txt | 25 +
.../zookeeper-client-c/Makefile.am | 19 +-
.../zookeeper-client-c/configure.ac | 30 +
.../zookeeper-client-c/include/proto.h | 1 +
.../zookeeper-client-c/include/zookeeper.h | 91 +++
.../zookeeper-client-c/src/zk_adaptor.h | 7 +
.../zookeeper-client-c/src/zk_sasl.c | 541 ++++++++++++++++++
.../zookeeper-client-c/src/zk_sasl.h | 154 +++++
.../zookeeper-client-c/src/zookeeper.c | 195 ++++++-
.../zookeeper-client-c/tests/TestSASLAuth.cc | 188 ++++++
.../tests/TestServerRequireClientSASLAuth.cc | 109 ----
.../zookeeper-client-c/tests/zkServer.sh | 7 +-
13 files changed, 1282 insertions(+), 139 deletions(-)
create mode 100644 tools/cmake/Modules/FindCyrusSASL.cmake
create mode 100644 zookeeper-client/zookeeper-client-c/src/zk_sasl.c
create mode 100644 zookeeper-client/zookeeper-client-c/src/zk_sasl.h
create mode 100644 zookeeper-client/zookeeper-client-c/tests/TestSASLAuth.cc
delete mode 100644 zookeeper-client/zookeeper-client-c/tests/TestServerRequireClientSASLAuth.cc
diff --git a/tools/cmake/Modules/FindCyrusSASL.cmake b/tools/cmake/Modules/FindCyrusSASL.cmake
new file mode 100644
index 00000000000..224a8cedc21
--- /dev/null
+++ b/tools/cmake/Modules/FindCyrusSASL.cmake
@@ -0,0 +1,54 @@
+# 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.
+#
+# - Find Cyrus SASL (sasl.h, libsasl2.so)
+#
+# This module defines
+# CYRUS_SASL_INCLUDE_DIR, directory containing headers
+# CYRUS_SASL_SHARED_LIB, path to Cyrus SASL's shared library
+# CYRUS_SASL_FOUND, whether Cyrus SASL and its plugins have been found
+#
+# It also defines the following IMPORTED targets:
+# CyrusSASL
+#
+# Hints:
+# Set CYRUS_SASL_ROOT_DIR to the root directory of a Cyrus SASL installation.
+#
+# The initial version of this file was extracted from
+# https://github.com/cloudera/kudu, at the following commit:
+#
+# commit 9806863e78107505a622b44112a897189d9b3c24
+# Author: Dan Burkert
+# Date: Mon Nov 30 12:15:36 2015 -0800
+#
+# Enable C++11
+
+find_path(CYRUS_SASL_INCLUDE_DIR sasl/sasl.h HINTS "${CYRUS_SASL_ROOT_DIR}/include")
+find_library(CYRUS_SASL_SHARED_LIB sasl2 HINTS "${CYRUS_SASL_ROOT_DIR}/lib")
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(CYRUS_SASL REQUIRED_VARS
+ CYRUS_SASL_SHARED_LIB CYRUS_SASL_INCLUDE_DIR)
+
+if(CYRUS_SASL_FOUND)
+ if(NOT TARGET CyrusSASL)
+ add_library(CyrusSASL UNKNOWN IMPORTED)
+ set_target_properties(CyrusSASL PROPERTIES
+ INTERFACE_INCLUDE_DIRECTORIES "${CYRUS_SASL_INCLUDE_DIR}"
+ IMPORTED_LINK_INTERFACE_LANGUAGES "C"
+ IMPORTED_LOCATION "${CYRUS_SASL_SHARED_LIB}")
+ endif()
+endif()
diff --git a/zookeeper-client/zookeeper-client-c/CMakeLists.txt b/zookeeper-client/zookeeper-client-c/CMakeLists.txt
index 06bbf983912..5883585f650 100644
--- a/zookeeper-client/zookeeper-client-c/CMakeLists.txt
+++ b/zookeeper-client/zookeeper-client-c/CMakeLists.txt
@@ -20,6 +20,8 @@ project(zookeeper VERSION 3.6.0)
set(email user@zookeeper.apache.org)
set(description "zookeeper C client")
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../../tools/cmake/Modules")
+
# general options
if(UNIX)
add_compile_options(-Wall -fPIC)
@@ -61,6 +63,20 @@ if(WANT_SOCK_CLOEXEC AND HAVE_SOCK_CLOEXEC)
set(SOCK_CLOEXEC_ENABLED 1)
endif()
+# Cyrus SASL 2.x
+option(WITH_CYRUS_SASL "turn ON/OFF Cyrus SASL 2.x support, or define SASL library location (default: ON)" ON)
+message("-- using WITH_CYRUS_SASL=${WITH_CYRUS_SASL}")
+if(NOT WITH_CYRUS_SASL STREQUAL "OFF")
+ if(NOT WITH_CYRUS_SASL STREQUAL "ON")
+ set(CYRUS_SASL_ROOT_DIR "${WITH_CYRUS_SASL}")
+ endif()
+ find_package(CyrusSASL)
+ if(CYRUS_SASL_FOUND)
+ message("-- Cyrus SASL 2.x found! will build with SASL support.")
+ else()
+ message("-- WARNING: unable to find Cyrus SASL 2.x! will build without SASL support.")
+ endif()
+endif()
# The function `to_have(in out)` converts a header name like `arpa/inet.h`
# into an Autotools style preprocessor definition `HAVE_ARPA_INET_H`.
@@ -171,6 +187,10 @@ else()
list(APPEND zookeeper_sources src/st_adaptor.c)
endif()
+if(CYRUS_SASL_FOUND)
+ list(APPEND zookeeper_sources src/zk_sasl.c)
+endif()
+
if(WIN32)
list(APPEND zookeeper_sources src/winport.c)
endif()
@@ -203,6 +223,11 @@ if(WANT_SYNCAPI AND NOT WIN32)
target_link_libraries(zookeeper PUBLIC Threads::Threads)
endif()
+if(CYRUS_SASL_FOUND)
+ target_compile_definitions(zookeeper PUBLIC HAVE_CYRUS_SASL_H)
+ target_link_libraries(zookeeper PUBLIC CyrusSASL)
+endif()
+
# cli executable
add_executable(cli src/cli.c)
target_link_libraries(cli zookeeper)
diff --git a/zookeeper-client/zookeeper-client-c/Makefile.am b/zookeeper-client/zookeeper-client-c/Makefile.am
index 34ef01208c5..80de6612912 100644
--- a/zookeeper-client/zookeeper-client-c/Makefile.am
+++ b/zookeeper-client/zookeeper-client-c/Makefile.am
@@ -13,10 +13,16 @@ if WANT_OPENSSL
OPENSSL_LIB_LDFLAGS = -lssl -lcrypto
endif
-AM_CPPFLAGS = -I${srcdir}/include -I${srcdir}/tests -I${srcdir}/generated $(SOLARIS_CPPFLAGS) $(OPENSSL_CPPFLAGS)
+if WANT_SASL
+ SASL_CPPFLAGS = -DHAVE_CYRUS_SASL_H
+ SASL_LIB_LDFLAGS = -lsasl2
+ SASL_SRC = src/zk_sasl.c
+endif
+
+AM_CPPFLAGS = -I${srcdir}/include -I${srcdir}/tests -I${srcdir}/generated $(SOLARIS_CPPFLAGS) $(OPENSSL_CPPFLAGS) $(SASL_CPPFLAGS)
AM_CFLAGS = -Wall -Werror -Wdeclaration-after-statement
AM_CXXFLAGS = -Wall $(USEIPV6)
-LIB_LDFLAGS = -no-undefined -version-info 2 $(SOLARIS_LIB_LDFLAGS) $(OPENSSL_LIB_LDFLAGS)
+LIB_LDFLAGS = -no-undefined -version-info 2 $(SOLARIS_LIB_LDFLAGS) $(OPENSSL_LIB_LDFLAGS) $(SASL_LIB_LDFLAGS)
# Additional flags for coverage testing (if enabled)
if ENABLEGCOV
@@ -37,7 +43,7 @@ COMMON_SRC = src/zookeeper.c include/zookeeper.h include/zookeeper_version.h inc
src/recordio.c include/recordio.h include/proto.h \
src/zk_adaptor.h generated/zookeeper.jute.c \
src/zk_log.c src/zk_hashtable.h src/zk_hashtable.c \
- src/addrvec.h src/addrvec.c
+ src/addrvec.h src/addrvec.c $(SASL_SRC)
# These are the symbols (classes, mostly) we want to export from our library.
EXPORT_SYMBOLS = '(zoo_|zookeeper_|zhandle|Z|format_log_message|log_message|logLevel|deallocate_|allocate_|zerror|is_unrecoverable)'
@@ -59,6 +65,7 @@ libzkmt_la_LIBADD = -lm $(CLOCK_GETTIME_LIBS)
lib_LTLIBRARIES += libzookeeper_mt.la
libzookeeper_mt_la_SOURCES =
+libzookeeper_mt_la_CFLAGS = -DTHREADED
libzookeeper_mt_la_LIBADD=libzkmt.la libhashtable.la -lpthread
libzookeeper_mt_la_DEPENDENCIES=libzkmt.la libhashtable.la
libzookeeper_mt_la_LDFLAGS = $(LIB_LDFLAGS) -export-symbols-regex $(EXPORT_SYMBOLS)
@@ -113,7 +120,7 @@ TEST_SOURCES = \
tests/ZooKeeperQuorumServer.h \
tests/TestReadOnlyClient.cc \
tests/TestLogClientEnv.cc \
- tests/TestServerRequireClientSASLAuth.cc \
+ tests/TestSASLAuth.cc \
$(NULL)
if SOLARIS
@@ -127,14 +134,14 @@ check_PROGRAMS = zktest-st
TESTS_ENVIRONMENT = ZKROOT=${srcdir}/../.. \
CLASSPATH=$$CLASSPATH:$$CLOVER_HOME/lib/clover*.jar
nodist_zktest_st_SOURCES = $(TEST_SOURCES)
-zktest_st_LDADD = libzkst.la libhashtable.la $(CPPUNIT_LIBS) $(OPENSSL_LIB_LDFLAGS) -ldl
+zktest_st_LDADD = libzkst.la libhashtable.la $(CPPUNIT_LIBS) $(OPENSSL_LIB_LDFLAGS) $(SASL_LIB_LDFLAGS) -ldl
zktest_st_CXXFLAGS = -DUSE_STATIC_LIB $(CPPUNIT_CFLAGS) $(USEIPV6) $(SOLARIS_CPPFLAGS)
zktest_st_LDFLAGS = -shared $(SYMBOL_WRAPPERS) $(SOLARIS_LIB_LDFLAGS)
if WANT_SYNCAPI
check_PROGRAMS += zktest-mt
nodist_zktest_mt_SOURCES = $(TEST_SOURCES) tests/PthreadMocks.cc
- zktest_mt_LDADD = libzkmt.la libhashtable.la -lpthread $(CPPUNIT_LIBS) $(OPENSSL_LIB_LDFLAGS) -ldl
+ zktest_mt_LDADD = libzkmt.la libhashtable.la -lpthread $(CPPUNIT_LIBS) $(OPENSSL_LIB_LDFLAGS) $(SASL_LIB_LDFLAGS) -ldl
zktest_mt_CXXFLAGS = -DUSE_STATIC_LIB -DTHREADED $(CPPUNIT_CFLAGS) $(USEIPV6)
if SOLARIS
SHELL_SYMBOL_WRAPPERS_MT = cat ${srcdir}/tests/wrappers-mt.opt
diff --git a/zookeeper-client/zookeeper-client-c/configure.ac b/zookeeper-client/zookeeper-client-c/configure.ac
index 96ddaeca03e..a2a234333e6 100644
--- a/zookeeper-client/zookeeper-client-c/configure.ac
+++ b/zookeeper-client/zookeeper-client-c/configure.ac
@@ -143,6 +143,36 @@ fi
AM_CONDITIONAL([WANT_SYNCAPI],[test "x$with_syncapi" != xno])
+dnl Cyrus SASL 2.x
+AC_ARG_WITH(sasl,
+ [AC_HELP_STRING([--with-sasl[=DIR]], [build with SASL support via Cyrus SASL 2.x (default=auto)])],
+ [], [with_sasl=yes])
+if test "x$with_sasl" != "xno"; then
+ saved_CPPFLAGS="$CPPFLAGS"
+ saved_LDFLAGS="$LDFLAGS"
+ if test "x$with_sasl" != "xyes" ; then
+ CPPFLAGS="$CPPFLAGS -I$with_sasl/include"
+ LDFLAGS="$LDFLAGS -L$with_sasl/lib"
+ fi
+ have_sasl=no
+ AC_CHECK_HEADER(sasl/sasl.h, [
+ AC_CHECK_LIB(sasl2, sasl_client_init, [have_sasl=yes])])
+ if test "x$have_sasl" != "xyes"; then
+ CPPFLAGS="$saved_CPPFLAGS"
+ LDFLAGS="$saved_LDFLAGS"
+ fi
+fi
+if test "x$with_sasl" != xno && test "x$have_sasl" = xno; then
+ AC_MSG_WARN([cannot build SASL support -- sasl2 not found])
+ with_sasl=no
+fi
+if test "x$with_sasl" != xno; then
+ AC_MSG_NOTICE([building with SASL support])
+else
+ AC_MSG_NOTICE([building without SASL support])
+fi
+AM_CONDITIONAL([WANT_SASL],[test "x$with_sasl" != xno])
+
# Checks for header files.
AC_HEADER_STDC
AC_CHECK_HEADERS([arpa/inet.h fcntl.h netdb.h netinet/in.h stdlib.h string.h sys/socket.h sys/time.h unistd.h sys/utsname.h])
diff --git a/zookeeper-client/zookeeper-client-c/include/proto.h b/zookeeper-client/zookeeper-client-c/include/proto.h
index 88774ff782e..65afde94d81 100644
--- a/zookeeper-client/zookeeper-client-c/include/proto.h
+++ b/zookeeper-client/zookeeper-client-c/include/proto.h
@@ -46,6 +46,7 @@ extern "C" {
#define ZOO_CLOSE_OP -11
#define ZOO_SETAUTH_OP 100
#define ZOO_SETWATCHES_OP 101
+#define ZOO_SASL_OP 102
#ifdef __cplusplus
}
diff --git a/zookeeper-client/zookeeper-client-c/include/zookeeper.h b/zookeeper-client/zookeeper-client-c/include/zookeeper.h
index 3a6a689a009..243fac28430 100644
--- a/zookeeper-client/zookeeper-client-c/include/zookeeper.h
+++ b/zookeeper-client/zookeeper-client-c/include/zookeeper.h
@@ -39,6 +39,10 @@
#include
#include
+#ifdef HAVE_CYRUS_SASL_H
+#include
+#endif /* HAVE_CYRUS_SASL_H */
+
#include "proto.h"
#include "zookeeper_version.h"
#include "recordio.h"
@@ -575,6 +579,93 @@ ZOOAPI zhandle_t *zookeeper_init2(const char *host, watcher_fn fn,
int recv_timeout, const clientid_t *clientid, void *context, int flags,
log_callback_fn log_callback);
+#ifdef HAVE_CYRUS_SASL_H
+
+/**
+ * \brief zoo_sasl_params structure.
+ *
+ * This structure holds the SASL parameters for the connection.
+ *
+ * Its \c service, \c host and \c callbacks fields are used with Cyrus
+ * SASL's \c sasl_client_new; its \c mechlist field with \c
+ * sasl_client_start. Please refer to these functions for precise
+ * semantics.
+ *
+ * Note while "string" parameters are copied into the ZooKeeper
+ * client, the callbacks array is simply referenced: its lifetime must
+ * therefore cover that of the handle.
+ */
+typedef struct zoo_sasl_params {
+ const char *service; /*!< The service name, usually "zookeeper" */
+ const char *host; /*!< The server name, e.g. "zk-sasl-md5" */
+ const char *mechlist; /*!< Mechanisms to try, e.g. "DIGEST-MD5" */
+ const sasl_callback_t *callbacks; /*!< List of callbacks */
+} zoo_sasl_params_t;
+
+/**
+ * \brief create a handle to communicate with zookeeper.
+ *
+ * This function is identical to \ref zookeeper_init2 except that it
+ * allows specifying optional SASL connection parameters. It is only
+ * available if the client library was configured to link against the
+ * Cyrus SASL library, and only visible when \c HAVE_CYRUS_SASL_H is defined.
+ *
+ * This method creates a new handle and a zookeeper session that corresponds
+ * to that handle. Session establishment is asynchronous, meaning that the
+ * session should not be considered established until (and unless) an
+ * event of state ZOO_CONNECTED_STATE is received.
+ * \param host comma separated host:port pairs, each corresponding to a zk
+ * server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002"
+ * \param fn the global watcher callback function. When notifications are
+ * triggered this function will be invoked.
+ * \param clientid the id of a previously established session that this
+ * client will be reconnecting to. Pass 0 if not reconnecting to a previous
+ * session. Clients can access the session id of an established, valid,
+ * connection by calling \ref zoo_client_id. If the session corresponding to
+ * the specified clientid has expired, or if the clientid is invalid for
+ * any reason, the returned zhandle_t will be invalid -- the zhandle_t
+ * state will indicate the reason for failure (typically
+ * ZOO_EXPIRED_SESSION_STATE).
+ * \param context the handback object that will be associated with this instance
+ * of zhandle_t. Application can access it (for example, in the watcher
+ * callback) using \ref zoo_get_context. The object is not used by zookeeper
+ * internally and can be null.
+ * \param flags reserved for future use. Should be set to zero.
+ * \param log_callback All log messages will be passed to this callback function.
+ * For more details see \ref zoo_get_log_callback and \ref zoo_set_log_callback.
+ * \param sasl_params a pointer to a \ref zoo_sasl_params_t structure
+ * specifying SASL connection parameters, or NULL to skip SASL
+ * authentication
+ * \return a pointer to the opaque zhandle structure. If it fails to create
+ * a new zhandle the function returns NULL and the errno variable
+ * indicates the reason.
+ */
+ZOOAPI zhandle_t *zookeeper_init_sasl(const char *host, watcher_fn fn,
+ int recv_timeout, const clientid_t *clientid, void *context, int flags,
+ log_callback_fn log_callback, zoo_sasl_params_t *sasl_params);
+
+/**
+ * \brief allocates and initializes a basic array of Cyrus SASL callbacks.
+ *
+ * This small helper function makes it easy to pass "static"
+ * parameters to Cyrus SASL's underlying callback-based API. Its use
+ * is not mandatory; you can still implement interactive dialogs by
+ * defining your own callbacks.
+ *
+ * \param user the "canned" response to \c SASL_CB_USER and \c SASL_CB_AUTHNAME,
+ * or NULL for none
+ * \param realm the "canned" response to \c SASL_CB_GETREALM, or NULL for none
+ * \param password_file the name of a file whose first line is read in
+ * response to \c SASL_CB_PASS, or NULL for none
+ * \return the freshly-malloc()ed callbacks array, or NULL if allocation
+ * failed. Deallocate with free(), but only after the corresponding
+ * ZooKeeper handle is closed.
+ */
+ZOOAPI sasl_callback_t *zoo_sasl_make_basic_callbacks(const char *user,
+ const char *realm, const char* password_file);
+
+#endif /* HAVE_CYRUS_SASL_H */
+
/**
* \brief update the list of servers this client will connect to.
*
diff --git a/zookeeper-client/zookeeper-client-c/src/zk_adaptor.h b/zookeeper-client/zookeeper-client-c/src/zk_adaptor.h
index 8157472dd4b..57696d4b203 100644
--- a/zookeeper-client/zookeeper-client-c/src/zk_adaptor.h
+++ b/zookeeper-client/zookeeper-client-c/src/zk_adaptor.h
@@ -18,6 +18,7 @@
#ifndef ZK_ADAPTOR_H_
#define ZK_ADAPTOR_H_
+
#include
#ifdef THREADED
#ifndef WIN32
@@ -182,6 +183,8 @@ typedef struct _auth_list_head {
#endif
} auth_list_head_t;
+typedef struct _zoo_sasl_client zoo_sasl_client_t;
+
/**
* This structure represents the connection to zookeeper.
*/
@@ -261,6 +264,10 @@ struct _zhandle {
/** used for chroot path at the client side **/
char *chroot;
+#ifdef HAVE_CYRUS_SASL_H
+ zoo_sasl_client_t *sasl_client;
+#endif /* HAVE_CYRUS_SASL_H */
+
/** Indicates if this client is allowed to go to r/o mode */
char allow_read_only;
/** Indicates if we connected to a majority server before */
diff --git a/zookeeper-client/zookeeper-client-c/src/zk_sasl.c b/zookeeper-client/zookeeper-client-c/src/zk_sasl.c
new file mode 100644
index 00000000000..e0ccfb31004
--- /dev/null
+++ b/zookeeper-client/zookeeper-client-c/src/zk_sasl.c
@@ -0,0 +1,541 @@
+/**
+ * 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 "config.h"
+
+#ifdef HAVE_SYS_SOCKET_H
+#include
+#endif
+
+#ifdef HAVE_NETINET_IN_H
+#include
+#endif
+
+#ifdef HAVE_ARPA_INET_H
+#include
+#endif
+
+#ifdef HAVE_NETDB_H
+#include
+#endif
+
+#ifdef HAVE_STRING_H
+#include
+#endif
+
+#ifdef HAVE_UNISTD_H
+#include
+#endif
+
+#include
+#include "zk_sasl.h"
+#include "zk_adaptor.h"
+#include "zookeeper_log.h"
+
+/*
+ * Store a duplicate of src, or NULL, into *target. Returns
+ * ZSYSTEMERROR if no memory could be allocated, ZOK otherwise.
+ */
+static int _zsasl_strdup(const char **target, const char *src)
+{
+ if (src) {
+ *target = strdup(src);
+ if (!*target) {
+ return ZSYSTEMERROR;
+ }
+ }
+ return ZOK;
+}
+
+/*
+ * Free the malloc'ed memory referenced by *location, setting
+ * *location to NULL.
+ */
+static void _zsasl_free(const char **location)
+{
+ if (*location) {
+ free((void*)*location);
+ *location = NULL;
+ }
+}
+
+zoo_sasl_client_t *zoo_sasl_client_create(zoo_sasl_params_t *sasl_params)
+{
+ zoo_sasl_client_t *sc = calloc(1, sizeof(*sc));
+ int rc = ZOK;
+
+ if (!sc) {
+ return NULL;
+ }
+
+ sc->state = ZOO_SASL_INITIAL;
+
+ rc = rc < 0 ? rc : _zsasl_strdup(&sc->params.service, sasl_params->service);
+ rc = rc < 0 ? rc : _zsasl_strdup(&sc->params.host, sasl_params->host);
+ rc = rc < 0 ? rc : _zsasl_strdup(&sc->params.mechlist, sasl_params->mechlist);
+
+ sc->params.callbacks = sasl_params->callbacks;
+
+ if (rc != ZOK) {
+ zoo_sasl_client_destroy(sc);
+ return NULL;
+ }
+
+ return sc;
+}
+
+void zoo_sasl_client_destroy(zoo_sasl_client_t *sc)
+{
+ if (!sc) {
+ return;
+ }
+
+ if (sc->conn) {
+ sasl_dispose(&sc->conn);
+ }
+
+ sc->params.callbacks = NULL;
+
+ _zsasl_free(&sc->params.service);
+ _zsasl_free(&sc->params.host);
+ _zsasl_free(&sc->params.mechlist);
+
+ sc->state = ZOO_SASL_FAILED;
+}
+
+void zoo_sasl_mark_failed(zhandle_t *zh)
+{
+ if (zh->sasl_client) {
+ zh->sasl_client->state = ZOO_SASL_FAILED;
+ }
+ zh->state = ZOO_AUTH_FAILED_STATE;
+}
+
+/*
+ * Put the handle and SASL client in failed state if rc is not ZOK.
+ * Returns rc.
+ */
+static int _zsasl_fail(zhandle_t *zh, int rc)
+{
+ if (rc != ZOK) {
+ zoo_sasl_mark_failed(zh);
+ LOG_ERROR(LOGCALLBACK(zh), "SASL authentication failed. rc=%d", rc);
+ }
+ return rc;
+}
+
+/*
+ * Get and format the host and port associated with a socket address
+ * into Cyrus SASL format. Optionally also store the host name in
+ * provided buffer.
+ *
+ * \param addr the socket address
+ * \param salen the length of addr
+ * \param ipport_buf the formatted output buffer, of size
+ * NI_MAXHOST + NI_MAXSERV
+ * \param opt_host_buf the host name buffer, of size NI_MAXHOST, or
+ * NULL for none
+ * \return ZOK if successful
+ */
+static int _zsasl_getipport(zhandle_t *zh,
+ const struct sockaddr *addr, socklen_t salen,
+ char *ipport_buf, char *opt_host_buf)
+{
+ char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV];
+ int niflags, error, written;
+
+ niflags = (NI_NUMERICHOST | NI_NUMERICSERV);
+#ifdef NI_WITHSCOPEID
+ if (addr->sa_family == AF_INET6) {
+ niflags |= NI_WITHSCOPEID;
+ }
+#endif
+ error = getnameinfo(addr, salen, hbuf, sizeof(hbuf), pbuf, sizeof(pbuf),
+ niflags);
+ if (error != 0) {
+ LOG_ERROR(LOGCALLBACK(zh), "getnameinfo: %s\n", gai_strerror(error));
+ return ZSYSTEMERROR;
+ }
+
+ written = sprintf(ipport_buf, "%s;%s", hbuf, pbuf);
+ if (written < 0) {
+ return ZSYSTEMERROR;
+ }
+
+ if (opt_host_buf) {
+ memcpy(opt_host_buf, hbuf, sizeof(hbuf));
+ }
+
+ return ZOK;
+}
+
+int zoo_sasl_connect(zhandle_t *zh)
+{
+ zoo_sasl_client_t *sc = zh->sasl_client;
+ char iplocalport[NI_MAXHOST + NI_MAXSERV];
+ char ipremoteport[NI_MAXHOST + NI_MAXSERV];
+ char host[NI_MAXHOST];
+ int rc, sr;
+ socklen_t salen;
+ struct sockaddr_storage local_ip, remote_ip;
+
+ if (!sc) {
+ return _zsasl_fail(zh, ZINVALIDSTATE);
+ }
+
+ if (sc->conn) {
+ sasl_dispose(&sc->conn);
+ }
+
+ sc->state = ZOO_SASL_INITIAL;
+
+ /* set ip addresses */
+ salen = sizeof(local_ip);
+ if (getsockname(zh->fd->sock, (struct sockaddr *)&local_ip, &salen) < 0) {
+ LOG_ERROR(LOGCALLBACK(zh), "getsockname");
+ return _zsasl_fail(zh, ZSYSTEMERROR);
+ }
+
+ rc = _zsasl_getipport(zh, (const struct sockaddr *)&local_ip, salen,
+ iplocalport, NULL);
+ if (rc < 0) {
+ return _zsasl_fail(zh, rc);
+ }
+
+ salen = sizeof(remote_ip);
+ if (getpeername(zh->fd->sock, (struct sockaddr *)&remote_ip, &salen) < 0) {
+ LOG_ERROR(LOGCALLBACK(zh), "getpeername");
+ return _zsasl_fail(zh, ZSYSTEMERROR);
+ }
+
+ rc = _zsasl_getipport(zh, (const struct sockaddr *)&remote_ip, salen,
+ ipremoteport, host);
+ if (rc < 0) {
+ return _zsasl_fail(zh, rc);
+ }
+
+ LOG_DEBUG(LOGCALLBACK(zh),
+ "Zookeeper Host: %s %s", iplocalport, ipremoteport);
+
+ /* client new connection */
+ sr = sasl_client_new(
+ sc->params.service,
+ sc->params.host ? sc->params.host : host,
+ iplocalport,
+ ipremoteport,
+ sc->params.callbacks,
+ /*secflags*/0,
+ &sc->conn);
+
+ if (sr != SASL_OK) {
+ LOG_ERROR(LOGCALLBACK(zh),
+ "allocating SASL connection state: %s",
+ sasl_errstring(sr, NULL, NULL));
+ return _zsasl_fail(zh, ZSYSTEMERROR);
+ }
+
+ return ZOK;
+}
+
+int zoo_sasl_client_start(zhandle_t *zh)
+{
+ zoo_sasl_client_t *sc = zh->sasl_client;
+ const char *chosenmech;
+ const char *client_data;
+ unsigned client_data_len;
+ int sr, rc = ZOK;
+
+ if (!sc || sc->state != ZOO_SASL_INITIAL) {
+ return _zsasl_fail(zh, ZINVALIDSTATE);
+ }
+
+ sc->state = ZOO_SASL_INTERMEDIATE;
+
+ sr = sasl_client_start(sc->conn, sc->params.mechlist,
+ NULL, &client_data, &client_data_len, &chosenmech);
+
+ if (sr != SASL_OK && sr != SASL_CONTINUE) {
+ LOG_ERROR(LOGCALLBACK(zh),
+ "Starting SASL negotiation: %s %s",
+ sasl_errstring(sr, NULL, NULL),
+ sasl_errdetail(sc->conn));
+ return _zsasl_fail(zh, ZSYSTEMERROR);
+ }
+
+ LOG_DEBUG(LOGCALLBACK(zh),
+ "SASL start sr:%d mech:%s client_data_len:%d",
+ sr, chosenmech, (int)client_data_len);
+
+ /*
+ * HACK: Without this, the SASL client is unable to reauthenticate
+ * with the ZooKeeper ensemble after a disconnect. This is due to
+ * a bug in the JDK's implementation of SASL DIGEST-MD5; the
+ * upstream issue is:
+ *
+ * JDK-6682540, Incorrect SASL DIGEST-MD5 behavior
+ * https://bugs.openjdk.java.net/browse/JDK-6682540
+ *
+ * A patch has been committed to the JDK in May 2019, but it will
+ * take a while to appear in production:
+ *
+ * http://hg.openjdk.java.net/jdk/jdk/rev/0627b8ad33c1
+ *
+ * As a workaround, we just "empty" the client start in DIGEST-MD5
+ * mode, forcing the server to proceed with initial (re)authentication.
+ */
+ if (client_data_len > 0 && strcmp(chosenmech, "DIGEST-MD5") == 0) {
+ LOG_DEBUG(LOGCALLBACK(zh),
+ "SASL start %s: refusing reauthenticate",
+ chosenmech);
+
+ client_data = NULL;
+ client_data_len = 0;
+ }
+
+ /*
+ * ZooKeeperSaslClient.java:285 says:
+ *
+ * GSSAPI: server sends a final packet after authentication
+ * succeeds or fails.
+ *
+ * so we need to keep track of that.
+ */
+ if (strcmp(chosenmech, "GSSAPI") == 0) {
+ sc->is_gssapi = 1;
+ }
+
+ if (sr == SASL_CONTINUE || client_data_len > 0) {
+ rc = queue_sasl_request(zh, client_data, client_data_len);
+ if (rc < 0) {
+ return _zsasl_fail(zh, rc);
+ }
+ }
+
+ return rc;
+}
+
+int zoo_sasl_client_step(zhandle_t *zh, const char *server_data,
+ int server_data_len)
+{
+ zoo_sasl_client_t *sc = zh->sasl_client;
+ const char *client_data;
+ unsigned client_data_len;
+ int sr, rc = ZOK;
+
+ if (!sc || sc->state != ZOO_SASL_INTERMEDIATE) {
+ return _zsasl_fail(zh, ZINVALIDSTATE);
+ }
+
+ LOG_DEBUG(LOGCALLBACK(zh),
+ "SASL intermediate server_data_len:%d", server_data_len);
+
+ if (sc->is_gssapi && sc->is_last_packet) {
+ /* See note in zoo_sasl_client_start. */
+ sc->is_last_packet = 0;
+ sc->state = ZOO_SASL_COMPLETE;
+ return rc;
+ }
+
+ sr = sasl_client_step(sc->conn, server_data, server_data_len,
+ NULL, &client_data, &client_data_len);
+
+ LOG_DEBUG(LOGCALLBACK(zh),
+ "SASL intermediate sr:%d client_data_len:%d",
+ sr, (int)client_data_len);
+
+ if (sr != SASL_OK && sr != SASL_CONTINUE) {
+ LOG_ERROR(LOGCALLBACK(zh),
+ "During SASL negotiation: %s %s",
+ sasl_errstring(sr, NULL, NULL),
+ sasl_errdetail(sc->conn));
+ return _zsasl_fail(zh, ZSYSTEMERROR);
+ }
+
+ if (sr == SASL_CONTINUE || client_data_len > 0) {
+ rc = queue_sasl_request(zh, client_data, client_data_len);
+ if (rc < 0) {
+ return _zsasl_fail(zh, rc);
+ }
+ }
+
+ if (sr != SASL_CONTINUE) {
+ if (sc->is_gssapi) {
+ /* See note in zoo_sasl_client_start. */
+ sc->is_last_packet = 1;
+ } else {
+ sc->state = ZOO_SASL_COMPLETE;
+ }
+ }
+
+ return rc;
+}
+
+/*
+ * Cyrus SASL callback for SASL_CB_GETREALM
+ */
+static int _zsasl_getrealm(void *context, int id, const char **availrealms,
+ const char **result)
+{
+ const char *realm = (const char*)context;
+ *result = realm;
+ return SASL_OK;
+}
+
+/*
+ * Cyrus SASL callback for SASL_CB_USER or SASL_CB_AUTHNAME
+ */
+static int _zsasl_simple(void *context, int id, const char **result,
+ unsigned *len)
+{
+ const char *user = (const char*)context;
+
+ /* paranoia check */
+ if (!result)
+ return SASL_BADPARAM;
+
+ switch (id) {
+ case SASL_CB_USER:
+ *result = user;
+ break;
+ case SASL_CB_AUTHNAME:
+ *result = user;
+ break;
+ default:
+ return SASL_BADPARAM;
+ }
+
+ return SASL_OK;
+}
+
+#ifndef HAVE_GETPASSPHRASE
+static char *
+getpassphrase(const char *prompt) {
+ return getpass(prompt);
+}
+#endif /* ! HAVE_GETPASSPHRASE */
+
+struct zsasl_secret_ctx {
+ const char *password_file;
+ sasl_secret_t *secret;
+};
+
+/*
+ * Cyrus SASL callback for SASL_CB_PASS
+ */
+static int _zsasl_getsecret(sasl_conn_t *conn, void *context, int id,
+ sasl_secret_t **psecret)
+{
+ struct zsasl_secret_ctx *secret_ctx = (struct zsasl_secret_ctx *)context;
+ char buf[1024];
+ char *password;
+ size_t len;
+ sasl_secret_t *x;
+
+ /* paranoia check */
+ if (!conn || !psecret || id != SASL_CB_PASS)
+ return SASL_BADPARAM;
+
+ if (secret_ctx->password_file) {
+ char *p;
+ FILE *fh = fopen(secret_ctx->password_file, "rt");
+ if (!fh)
+ return SASL_FAIL;
+
+ if (!fgets(buf, sizeof(buf), fh)) {
+ fclose(fh);
+ return SASL_FAIL;
+ }
+
+ fclose(fh);
+
+ p = strrchr(buf, '\n');
+ if (p)
+ *p = '\0';
+
+ password = buf;
+ } else {
+ password = getpassphrase("Password: ");
+
+ if (!password)
+ return SASL_FAIL;
+ }
+
+ len = strlen(password);
+
+ x = secret_ctx->secret = (sasl_secret_t *)realloc(
+ secret_ctx->secret, sizeof(sasl_secret_t) + len);
+
+ if (!x) {
+ memset(password, 0, len);
+ return SASL_NOMEM;
+ }
+
+ x->len = len;
+ strcpy((char *) x->data, password);
+ memset(password, 0, len);
+
+ *psecret = x;
+ return SASL_OK;
+}
+
+typedef int (* sasl_callback_fn_t)(void);
+
+sasl_callback_t *zoo_sasl_make_basic_callbacks(const char *user,
+ const char *realm,
+ const char* password_file)
+{
+ struct zsasl_secret_ctx *secret_ctx;
+ const char *user_ctx = NULL;
+ const char *realm_ctx = NULL;
+ int rc;
+
+ secret_ctx = (struct zsasl_secret_ctx *)calloc(
+ 1, sizeof(struct zsasl_secret_ctx));
+ rc = secret_ctx ? ZOK : ZSYSTEMERROR;
+
+ rc = rc < 0 ? rc : _zsasl_strdup(&user_ctx, user);
+ rc = rc < 0 ? rc : _zsasl_strdup(&realm_ctx, realm);
+ rc = rc < 0 ? rc : _zsasl_strdup(&secret_ctx->password_file, password_file);
+
+ {
+ sasl_callback_t callbacks[] = {
+ { SASL_CB_GETREALM, (sasl_callback_fn_t)&_zsasl_getrealm, (void*)realm_ctx },
+ { SASL_CB_USER, (sasl_callback_fn_t)&_zsasl_simple, (void*)user_ctx },
+ { SASL_CB_AUTHNAME, (sasl_callback_fn_t)&_zsasl_simple, (void*)user_ctx },
+ { SASL_CB_PASS, (sasl_callback_fn_t)&_zsasl_getsecret, (void*)secret_ctx },
+ { SASL_CB_LIST_END, NULL, NULL }
+ };
+
+ sasl_callback_t *xcallbacks = rc < 0 ? NULL : malloc(sizeof(callbacks));
+
+ if (rc < 0 || !xcallbacks) {
+ if (secret_ctx) {
+ _zsasl_free(&secret_ctx->password_file);
+ free(secret_ctx);
+ secret_ctx = NULL;
+ }
+ _zsasl_free(&realm_ctx);
+ _zsasl_free(&user_ctx);
+ return NULL;
+ }
+
+ memcpy(xcallbacks, callbacks, sizeof(callbacks));
+
+ return xcallbacks;
+ }
+}
diff --git a/zookeeper-client/zookeeper-client-c/src/zk_sasl.h b/zookeeper-client/zookeeper-client-c/src/zk_sasl.h
new file mode 100644
index 00000000000..cb840f12cde
--- /dev/null
+++ b/zookeeper-client/zookeeper-client-c/src/zk_sasl.h
@@ -0,0 +1,154 @@
+/**
+ * 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.
+ */
+
+#ifndef ZK_SASL_H_
+#define ZK_SASL_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \brief enumerates SASL authentication states. Corresponds to
+ * org.apache.zookeeper.client.ZooKeeperSaslClient.SaslState.
+ */
+typedef enum {
+ ZOO_SASL_INITIAL,
+ ZOO_SASL_INTERMEDIATE,
+ ZOO_SASL_COMPLETE,
+ ZOO_SASL_FAILED
+} ZooSaslState;
+
+/**
+ * \brief zoo_sasl_client_t structure.
+ *
+ * This structure holds (a copy of) the original SASL parameters, the
+ * Cyrus SASL client "object," and the current authentication state.
+ *
+ * See \ref zoo_sasl_client_create and \ref zoo_sasl_client_destroy.
+ */
+typedef struct _zoo_sasl_client {
+ zoo_sasl_params_t params;
+ sasl_conn_t *conn;
+ ZooSaslState state;
+ unsigned char is_gssapi;
+ unsigned char is_last_packet;
+} zoo_sasl_client_t;
+
+/**
+ * \brief allocates a \ref zoo_sasl_client_t "object."
+ *
+ * \param sasl_params The SASL parameters to use. Note while "string"
+ * parameters are copied, the callbacks array is simply referenced:
+ * its lifetime must therefore cover that of the handle.
+ * \return the client object, or NULL on failure
+ */
+zoo_sasl_client_t *zoo_sasl_client_create(zoo_sasl_params_t *sasl_params);
+
+/**
+ * \brief "destroys" a \ref zoo_sasl_client_t "object" allocated by
+ * \ref zoo_sasl_client_create.
+ *
+ * \param sasl_client the client "object"
+ */
+void zoo_sasl_client_destroy(zoo_sasl_client_t *sasl_client);
+
+/**
+ * \brief put the handle and SASL client in failed state.
+ *
+ * This sets the SASL client in \ref ZOO_SASL_FAILED state and the
+ * ZooKeeper handle in \ref ZOO_AUTH_FAILED_STATE state.
+ *
+ * \param zh the ZooKeeper handle to mark
+ */
+void zoo_sasl_mark_failed(zhandle_t *zh);
+
+/**
+ * \brief prepares the SASL connection object for the (connecting)
+ * ZooKeeper handle.
+ *
+ * The client is switched to \ref ZOO_SASL_INITIAL state, or \ref
+ * ZOO_SASL_FAILED in case of error.
+ *
+ * \param zh the ZooKeeper handle in \ref ZOO_CONNECTING_STATE state
+ * \return ZOK on success, or one of the following on failure:
+ * ZINVALIDSTATE - no SASL client present
+ * ZSYSTEMERROR - SASL library error
+ */
+int zoo_sasl_connect(zhandle_t *zh);
+
+/**
+ * \brief queues an encoded SASL request to ZooKeeper.
+ *
+ * Note that such packets are added to the front of the queue,
+ * pre-empting "normal" communications.
+ *
+ * \param zh the ZooKeeper handle
+ * \param client_data the encoded SASL data, ready to send
+ * \param client_data_len the length of \c client_data
+ * \return ZOK on success, or ZMARSHALLINGERROR if something went wrong
+ */
+int queue_sasl_request(zhandle_t *zh, const char *client_data,
+ int client_data_len);
+
+/**
+ * \brief starts a new SASL authentication session using the
+ * parameters provided to \ref zoo_sasl_client_create
+ *
+ * On entry, the client must be in \ref ZOO_SASL_INITIAL state; this
+ * call switches it to \ref ZOO_SASL_INTERMEDIATE state or \ref
+ * ZOO_SASL_FAILED in case of error.
+ *
+ * Note that this is not a "normal" ZK client function; the
+ * corresponding packets are added to the front of the queue,
+ * pre-empting other requests.
+ *
+ * \param zh the ZooKeeper handle, with the SASL client in
+ * \ref ZOO_SASL_INITIAL state
+ * \return ZOK on success, or one of the following on failure:
+ * ZINVALIDSTATE - client not in expected state
+ * ZSYSTEMERROR - SASL library error
+ * ZMARSHALLINGERROR - failed to marshall a request; possibly, out of memory
+ */
+int zoo_sasl_client_start(zhandle_t *zh);
+
+/**
+ * \brief performs a step in the SASL authentication process.
+ *
+ * On entry, the client must be in \ref ZOO_SASL_INTERMEDIATE
+ * state. This call switches it to \ref ZOO_SASL_COMPLETE state if
+ * (and only if) the process is complete--or to \ref ZOO_SASL_FAILED
+ * in case of error.
+ *
+ * \param zh the ZooKeeper handle, with the SASL client in
+ * \ref ZOO_SASL_INTERMEDIATE state
+ * \param server_data SASL data from the ZooKeeper server
+ * \param server_data_len length of \c server_data
+ * \return ZOK on success, or one of the following on failure:
+ * ZINVALIDSTATE - client not in expected state
+ * ZSYSTEMERROR - SASL library error
+ * ZMARSHALLINGERROR - failed to marshall a request; possibly, out of memory
+ */
+int zoo_sasl_client_step(zhandle_t *zh, const char *server_data,
+ int server_data_len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /*ZK_SASL_H_*/
diff --git a/zookeeper-client/zookeeper-client-c/src/zookeeper.c b/zookeeper-client/zookeeper-client-c/src/zookeeper.c
index 7ab5eed33cb..db42d6cccb3 100644
--- a/zookeeper-client/zookeeper-client-c/src/zookeeper.c
+++ b/zookeeper-client/zookeeper-client-c/src/zookeeper.c
@@ -32,6 +32,10 @@
#include "zookeeper_log.h"
#include "zk_hashtable.h"
+#ifdef HAVE_CYRUS_SASL_H
+#include "zk_sasl.h"
+#endif /* HAVE_CYRUS_SASL_H */
+
#include
#include
#include
@@ -326,6 +330,17 @@ static void zookeeper_set_sock_noblock(zhandle_t *, socket_t);
static void zookeeper_set_sock_timeout(zhandle_t *, socket_t, int);
static socket_t zookeeper_connect(zhandle_t *, struct sockaddr_storage *, socket_t);
+/*
+ * return 1 if zh has a SASL client performing authentication, 0 otherwise.
+ */
+static int is_sasl_auth_in_progress(zhandle_t* zh)
+{
+#ifdef HAVE_CYRUS_SASL_H
+ return zh->sasl_client && zh->sasl_client->state == ZOO_SASL_INTERMEDIATE;
+#else /* !HAVE_CYRUS_SASL_H */
+ return 0;
+#endif /* HAVE_CYRUS_SASL_H */
+}
/*
* abort due to the use of a sync api in a singlethreaded environment
@@ -639,6 +654,13 @@ static void destroy(zhandle_t *zh)
destroy_zk_hashtable(zh->active_child_watchers);
addrvec_free(&zh->addrs_old);
addrvec_free(&zh->addrs_new);
+
+#ifdef HAVE_CYRUS_SASL_H
+ if (zh->sasl_client) {
+ zoo_sasl_client_destroy(zh->sasl_client);
+ zh->sasl_client = NULL;
+ }
+#endif /* HAVE_CYRUS_SASL_H */
}
static void setup_random()
@@ -1197,7 +1219,7 @@ static void log_env(zhandle_t *zh) {
*/
static zhandle_t *zookeeper_init_internal(const char *host, watcher_fn watcher,
int recv_timeout, const clientid_t *clientid, void *context, int flags,
- log_callback_fn log_callback, zcert_t *cert)
+ log_callback_fn log_callback, zcert_t *cert, void *sasl_params)
{
int errnosave = 0;
zhandle_t *zh = NULL;
@@ -1308,6 +1330,16 @@ static zhandle_t *zookeeper_init_internal(const char *host, watcher_fn watcher,
zh->active_child_watchers=create_zk_hashtable();
zh->disable_reconnection_attempt = 0;
+#ifdef HAVE_CYRUS_SASL_H
+ if (sasl_params) {
+ zh->sasl_client = zoo_sasl_client_create(
+ (zoo_sasl_params_t*)sasl_params);
+ if (!zh->sasl_client) {
+ goto abort;
+ }
+ }
+#endif /* HAVE_CYRUS_SASL_H */
+
if (adaptor_init(zh) == -1) {
goto abort;
}
@@ -1325,14 +1357,14 @@ static zhandle_t *zookeeper_init_internal(const char *host, watcher_fn watcher,
zhandle_t *zookeeper_init(const char *host, watcher_fn watcher,
int recv_timeout, const clientid_t *clientid, void *context, int flags)
{
- return zookeeper_init_internal(host, watcher, recv_timeout, clientid, context, flags, NULL, NULL);
+ return zookeeper_init_internal(host, watcher, recv_timeout, clientid, context, flags, NULL, NULL, NULL);
}
zhandle_t *zookeeper_init2(const char *host, watcher_fn watcher,
int recv_timeout, const clientid_t *clientid, void *context, int flags,
log_callback_fn log_callback)
{
- return zookeeper_init_internal(host, watcher, recv_timeout, clientid, context, flags, log_callback, NULL);
+ return zookeeper_init_internal(host, watcher, recv_timeout, clientid, context, flags, log_callback, NULL, NULL);
}
#ifdef HAVE_OPENSSL_H
@@ -1345,10 +1377,19 @@ zhandle_t *zookeeper_init_ssl(const char *host, const char *cert, watcher_fn wat
zcert.cert = strtok(NULL, ",");
zcert.key = strtok(NULL, ",");
zcert.passwd = strtok(NULL, ",");
- return zookeeper_init_internal(host, watcher, recv_timeout, clientid, context, flags, NULL, &zcert);
+ return zookeeper_init_internal(host, watcher, recv_timeout, clientid, context, flags, NULL, &zcert, NULL);
}
#endif
+#ifdef HAVE_CYRUS_SASL_H
+zhandle_t *zookeeper_init_sasl(const char *host, watcher_fn watcher,
+ int recv_timeout, const clientid_t *clientid, void *context, int flags,
+ log_callback_fn log_callback, zoo_sasl_params_t *sasl_params)
+{
+ return zookeeper_init_internal(host, watcher, recv_timeout, clientid, context, flags, log_callback, NULL, sasl_params);
+}
+#endif /* HAVE_CYRUS_SASL_H */
+
/**
* Set a new list of zk servers to connect to. Disconnect will occur if
* current connection endpoint is not in the list.
@@ -2540,7 +2581,7 @@ int zookeeper_interest(zhandle_t *zh, socket_t *fd, int *interest,
*interest = ZOOKEEPER_READ;
/* we are interested in a write if we are connected and have something
* to send, or we are waiting for a connect to finish. */
- if ((zh->to_send.head && is_connected(zh))
+ if ((zh->to_send.head && (is_connected(zh) || is_sasl_auth_in_progress(zh)))
|| zh->state == ZOO_CONNECTING_STATE
|| zh->state == ZOO_SSL_CONNECTING_STATE) {
*interest |= ZOOKEEPER_WRITE;
@@ -2691,6 +2732,91 @@ static int init_ssl_for_socket(zsock_t *fd, zhandle_t *zh, int fail_on_error) {
#endif
+/*
+ * the "bottom half" of the session establishment procedure, executed
+ * either after receiving the "prime response," or after SASL
+ * authentication is complete
+ */
+static void finalize_session_establishment(zhandle_t *zh) {
+ zh->state = zh->primer_storage.readOnly ?
+ ZOO_READONLY_STATE : ZOO_CONNECTED_STATE;
+ zh->reconfig = 0;
+ LOG_INFO(LOGCALLBACK(zh),
+ "session establishment complete on server %s, sessionId=%#llx, negotiated timeout=%d %s",
+ format_endpoint_info(&zh->addr_cur),
+ zh->client_id.client_id, zh->recv_timeout,
+ zh->primer_storage.readOnly ? "(READ-ONLY mode)" : "");
+ /* we want the auth to be sent for, but since both call push to front
+ we need to call send_watch_set first */
+ send_set_watches(zh);
+ /* send the authentication packet now */
+ send_auth_info(zh);
+ LOG_DEBUG(LOGCALLBACK(zh), "Calling a watcher for a ZOO_SESSION_EVENT and the state=ZOO_CONNECTED_STATE");
+ zh->input_buffer = 0; // just in case the watcher calls zookeeper_process() again
+ PROCESS_SESSION_EVENT(zh, zh->state);
+}
+
+#ifdef HAVE_CYRUS_SASL_H
+
+/*
+ * queue an encoded SASL request to ZooKeeper. The packet is added to
+ * the front of the queue.
+ *
+ * \param zh the ZooKeeper handle
+ * \param client_data the encoded SASL data, ready to send
+ * \param client_data_len the length of \c client_data
+ * \return ZOK on success, or ZMARSHALLINGERROR if something went wrong
+ */
+int queue_sasl_request(zhandle_t *zh, const char *client_data, int client_data_len)
+{
+ struct oarchive *oa;
+ int rc;
+
+ /* Java client use normal xid, too. */
+ struct RequestHeader h = { get_xid(), ZOO_SASL_OP };
+ struct GetSASLRequest req = { { client_data_len, client_data_len>0 ? (char *) client_data : "" } };
+
+ oa = create_buffer_oarchive();
+ rc = serialize_RequestHeader(oa, "header", &h);
+ rc = rc < 0 ? rc : serialize_GetSASLRequest(oa, "req", &req);
+ rc = rc < 0 ? rc : queue_front_buffer_bytes(&zh->to_send, get_buffer(oa),
+ get_buffer_len(oa));
+ close_buffer_oarchive(&oa, 0);
+
+ LOG_DEBUG(LOGCALLBACK(zh),
+ "SASL: Queued request len=%d rc=%d", client_data_len, rc);
+
+ return (rc < 0) ? ZMARSHALLINGERROR : ZOK;
+}
+
+/*
+ * decode an expected SASL response and perform the corresponding
+ * authentication step
+ */
+static int process_sasl_response(zhandle_t *zh, char *buffer, int len)
+{
+ struct iarchive *ia = create_buffer_iarchive(buffer, len);
+ struct ReplyHeader hdr;
+ struct SetSASLResponse res;
+ int rc;
+
+ rc = ia ? ZOK : ZSYSTEMERROR;
+ rc = rc < 0 ? rc : deserialize_ReplyHeader(ia, "hdr", &hdr);
+ rc = rc < 0 ? rc : deserialize_SetSASLResponse(ia, "reply", &res);
+ rc = rc < 0 ? rc : zoo_sasl_client_step(zh, res.token.buff, res.token.len);
+ deallocate_SetSASLResponse(&res);
+ if (ia) {
+ close_buffer_iarchive(&ia);
+ }
+
+ LOG_DEBUG(LOGCALLBACK(zh),
+ "SASL: Processed response len=%d rc=%d", len, rc);
+
+ return rc;
+}
+
+#endif /* HAVE_CYRUS_SASL_H */
+
static int check_events(zhandle_t *zh, int events)
{
if (zh->fd->sock == -1)
@@ -2759,7 +2885,24 @@ static int check_events(zhandle_t *zh, int events)
if (rc > 0) {
get_system_time(&zh->last_recv);
if (zh->input_buffer != &zh->primer_buffer) {
- queue_buffer(&zh->to_process, zh->input_buffer, 0);
+ if (is_connected(zh) || !is_sasl_auth_in_progress(zh)) {
+ queue_buffer(&zh->to_process, zh->input_buffer, 0);
+#ifdef HAVE_CYRUS_SASL_H
+ } else {
+ rc = process_sasl_response(zh, zh->input_buffer->buffer, zh->input_buffer->curr_offset);
+ free_buffer(zh->input_buffer);
+ if (rc < 0) {
+ zoo_sasl_mark_failed(zh);
+ return rc;
+ } else if (zh->sasl_client->state == ZOO_SASL_COMPLETE) {
+ /*
+ * SASL authentication just completed; send
+ * watches, auth. info, etc. now.
+ */
+ finalize_session_establishment(zh);
+ }
+#endif /* HAVE_CYRUS_SASL_H */
+ }
} else {
int64_t oldid, newid;
//deserialize
@@ -2780,22 +2923,28 @@ static int check_events(zhandle_t *zh, int events)
memcpy(zh->client_id.passwd, &zh->primer_storage.passwd,
sizeof(zh->client_id.passwd));
- zh->state = zh->primer_storage.readOnly ?
- ZOO_READONLY_STATE : ZOO_CONNECTED_STATE;
- zh->reconfig = 0;
- LOG_INFO(LOGCALLBACK(zh),
- "session establishment complete on server %s, sessionId=%#llx, negotiated timeout=%d %s",
- format_endpoint_info(&zh->addr_cur),
- newid, zh->recv_timeout,
- zh->primer_storage.readOnly ? "(READ-ONLY mode)" : "");
- /* we want the auth to be sent for, but since both call push to front
- we need to call send_watch_set first */
- send_set_watches(zh);
- /* send the authentication packet now */
- send_auth_info(zh);
- LOG_DEBUG(LOGCALLBACK(zh), "Calling a watcher for a ZOO_SESSION_EVENT and the state=ZOO_CONNECTED_STATE");
- zh->input_buffer = 0; // just in case the watcher calls zookeeper_process() again
- PROCESS_SESSION_EVENT(zh, zh->state);
+
+#ifdef HAVE_CYRUS_SASL_H
+ if (zh->sasl_client) {
+ /*
+ * Start a SASL authentication session.
+ * Watches, auth. info, etc. will be sent
+ * after it completes.
+ */
+ rc = zoo_sasl_connect(zh);
+ rc = rc < 0 ? rc : zoo_sasl_client_start(zh);
+ if (rc < 0) {
+ zoo_sasl_mark_failed(zh);
+ return rc;
+ }
+ } else {
+ /* Can send watches, auth. info, etc. immediately. */
+ finalize_session_establishment(zh);
+ }
+#else /* HAVE_CYRUS_SASL_H */
+ /* Can send watches, auth. info, etc. immediately. */
+ finalize_session_establishment(zh);
+#endif /* HAVE_CYRUS_SASL_H */
}
}
zh->input_buffer = 0;
@@ -4593,7 +4742,7 @@ int flush_send_queue(zhandle_t*zh, int timeout)
// we use a recursive lock instead and only dequeue the buffer if a send was
// successful
lock_buffer_list(&zh->to_send);
- while (zh->to_send.head != 0 && is_connected(zh)) {
+ while (zh->to_send.head != 0 && (is_connected(zh) || is_sasl_auth_in_progress(zh))) {
if(timeout!=0){
#ifndef _WIN32
struct pollfd fds;
diff --git a/zookeeper-client/zookeeper-client-c/tests/TestSASLAuth.cc b/zookeeper-client/zookeeper-client-c/tests/TestSASLAuth.cc
new file mode 100644
index 00000000000..4b2c5ec1ef5
--- /dev/null
+++ b/zookeeper-client/zookeeper-client-c/tests/TestSASLAuth.cc
@@ -0,0 +1,188 @@
+/**
+ * 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.
+ */
+
+#ifdef THREADED
+
+#include
+#include "CppAssertHelper.h"
+
+#include
+#include
+
+#include
+
+#include "Util.h"
+#include "WatchUtil.h"
+
+class Zookeeper_SASLAuth : public CPPUNIT_NS::TestFixture {
+ CPPUNIT_TEST_SUITE(Zookeeper_SASLAuth);
+ CPPUNIT_TEST(testServerRequireClientSASL);
+#ifdef HAVE_CYRUS_SASL_H
+ CPPUNIT_TEST(testClientSASL);
+#endif /* HAVE_CYRUS_SASL_H */
+ CPPUNIT_TEST_SUITE_END();
+ FILE *logfile;
+ static const char hostPorts[];
+ static const char jaasConf[];
+ static void watcher(zhandle_t *, int type, int state, const char *path,void*v){
+ watchctx_t *ctx = (watchctx_t*)v;
+
+ if (state == ZOO_CONNECTED_STATE) {
+ ctx->connected = true;
+ } else {
+ ctx->connected = false;
+ }
+ if (type != ZOO_SESSION_EVENT) {
+ evt_t evt;
+ evt.path = path;
+ evt.type = type;
+ ctx->putEvent(evt);
+ }
+ }
+
+public:
+ Zookeeper_SASLAuth() {
+ logfile = openlogfile("Zookeeper_SASLAuth");
+ }
+
+ ~Zookeeper_SASLAuth() {
+ if (logfile) {
+ fflush(logfile);
+ fclose(logfile);
+ logfile = 0;
+ }
+ }
+
+ void setUp() {
+ zoo_set_log_stream(logfile);
+
+ // Create SASL configuration file for server.
+ FILE *conff = fopen("Zookeeper_SASLAuth.jaas.conf", "wt");
+ CPPUNIT_ASSERT(conff);
+ size_t confLen = strlen(jaasConf);
+ CPPUNIT_ASSERT_EQUAL(fwrite(jaasConf, 1, confLen, conff), confLen);
+ CPPUNIT_ASSERT_EQUAL(fclose(conff), 0);
+ conff = NULL;
+
+ // Create password file for client.
+ FILE *passf = fopen("Zookeeper_SASLAuth.password", "wt");
+ CPPUNIT_ASSERT(passf);
+ CPPUNIT_ASSERT(fputs("mypassword", passf) > 0);
+ CPPUNIT_ASSERT_EQUAL(fclose(passf), 0);
+ passf = NULL;
+ }
+
+ void startServer(bool useJaasConf = true) {
+ char cmd[1024];
+ sprintf(cmd, "%s startRequireSASLAuth%s%s",
+ ZKSERVER_CMD,
+ useJaasConf ? " " : "",
+ useJaasConf ? "Zookeeper_SASLAuth.jaas.conf" : "");
+ CPPUNIT_ASSERT(system(cmd) == 0);
+ }
+
+ void stopServer() {
+ char cmd[1024];
+ sprintf(cmd, "%s stop", ZKSERVER_CMD);
+ CPPUNIT_ASSERT(system(cmd) == 0);
+ }
+
+ void testServerRequireClientSASL() {
+ startServer(false);
+
+ watchctx_t ctx;
+ int rc = 0;
+ zhandle_t *zk = zookeeper_init(hostPorts, watcher, 10000, 0, &ctx, 0);
+ ctx.zh = zk;
+ CPPUNIT_ASSERT(zk);
+
+ // Wait for handle to be connected.
+ CPPUNIT_ASSERT(ctx.waitForConnected(zk));
+
+ char pathbuf[80];
+ struct Stat stat_a = {0};
+
+ rc = zoo_create2(zk, "/serverRequireClientSASL", "", 0,
+ &ZOO_OPEN_ACL_UNSAFE, 0, pathbuf, sizeof(pathbuf), &stat_a);
+ CPPUNIT_ASSERT_EQUAL((int)ZSESSIONCLOSEDREQUIRESASLAUTH, rc);
+
+ stopServer();
+ }
+
+#ifdef HAVE_CYRUS_SASL_H
+ void testClientSASL() {
+ startServer();
+
+ // Initialize Cyrus SASL.
+ CPPUNIT_ASSERT_EQUAL(sasl_client_init(NULL), SASL_OK);
+
+ // Initialize SASL parameters.
+ zoo_sasl_params_t sasl_params = { 0 };
+
+ sasl_params.service = "zookeeper";
+ sasl_params.host = "zk-sasl-md5";
+ sasl_params.mechlist = "DIGEST-MD5";
+ sasl_params.callbacks = zoo_sasl_make_basic_callbacks(
+ "myuser", NULL, "Zookeeper_SASLAuth.password");
+
+ // Connect.
+ watchctx_t ctx;
+ int rc = 0;
+ zhandle_t *zk = zookeeper_init_sasl(hostPorts, watcher, 10000, NULL,
+ &ctx, /*flags*/0, /*log_callback*/NULL, &sasl_params);
+ ctx.zh = zk;
+ CPPUNIT_ASSERT(zk);
+
+ // Wait for SASL auth to complete and handle to be connected.
+ CPPUNIT_ASSERT(ctx.waitForConnected(zk));
+
+ // Leave mark.
+ char pathbuf[80];
+ struct Stat stat_a = {0};
+ rc = zoo_create2(zk, "/serverRequireClientSASL", "", 0,
+ &ZOO_OPEN_ACL_UNSAFE, 0, pathbuf, sizeof(pathbuf), &stat_a);
+ CPPUNIT_ASSERT_EQUAL((int)ZOK, rc);
+
+ // Stop and restart the server to test automatic reconnect & re-auth.
+ stopServer();
+ CPPUNIT_ASSERT(ctx.waitForDisconnected(zk));
+ startServer();
+
+ // Wait for automatic SASL re-auth to complete.
+ CPPUNIT_ASSERT(ctx.waitForConnected(zk));
+
+ // Check mark left above.
+ rc = zoo_exists(zk, "/serverRequireClientSASL", /*watch*/false, &stat_a);
+ CPPUNIT_ASSERT_EQUAL((int)ZOK, rc);
+
+ stopServer();
+ }
+#endif /* HAVE_CYRUS_SASL_H */
+};
+
+const char Zookeeper_SASLAuth::hostPorts[] = "127.0.0.1:22181";
+
+const char Zookeeper_SASLAuth::jaasConf[] =
+ "Server {\n"
+ " org.apache.zookeeper.server.auth.DigestLoginModule required\n"
+ " user_myuser=\"mypassword\";\n"
+ "};\n";
+
+CPPUNIT_TEST_SUITE_REGISTRATION(Zookeeper_SASLAuth);
+
+#endif /* THREADED */
diff --git a/zookeeper-client/zookeeper-client-c/tests/TestServerRequireClientSASLAuth.cc b/zookeeper-client/zookeeper-client-c/tests/TestServerRequireClientSASLAuth.cc
deleted file mode 100644
index 294388874f2..00000000000
--- a/zookeeper-client/zookeeper-client-c/tests/TestServerRequireClientSASLAuth.cc
+++ /dev/null
@@ -1,109 +0,0 @@
-/**
- * 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
-#include "CppAssertHelper.h"
-
-#include
-#include
-
-#include
-
-#include "Util.h"
-#include "WatchUtil.h"
-
-ZOOAPI int zoo_create2(zhandle_t *zh, const char *path, const char *value,
- int valuelen, const struct ACL_vector *acl, int mode,
- char *path_buffer, int path_buffer_len, struct Stat *stat);
-
-class Zookeeper_serverRequireClientSASL : public CPPUNIT_NS::TestFixture {
- CPPUNIT_TEST_SUITE(Zookeeper_serverRequireClientSASL);
-#ifdef THREADED
- CPPUNIT_TEST(testServerRequireClientSASL);
-#endif
- CPPUNIT_TEST_SUITE_END();
- FILE *logfile;
- static const char hostPorts[];
- static void watcher(zhandle_t *, int type, int state, const char *path,void*v){
- watchctx_t *ctx = (watchctx_t*)v;
-
- if (state == ZOO_CONNECTED_STATE) {
- ctx->connected = true;
- } else {
- ctx->connected = false;
- }
- if (type != ZOO_SESSION_EVENT) {
- evt_t evt;
- evt.path = path;
- evt.type = type;
- ctx->putEvent(evt);
- }
- }
-
-public:
- Zookeeper_serverRequireClientSASL() {
- logfile = openlogfile("Zookeeper_serverRequireClientSASL");
- }
-
- ~Zookeeper_serverRequireClientSASL() {
- if (logfile) {
- fflush(logfile);
- fclose(logfile);
- logfile = 0;
- }
- }
-
- void setUp() {
- zoo_set_log_stream(logfile);
- }
-
- void startServer() {
- char cmd[1024];
- sprintf(cmd, "%s startRequireSASLAuth", ZKSERVER_CMD);
- CPPUNIT_ASSERT(system(cmd) == 0);
- }
-
- void stopServer() {
- char cmd[1024];
- sprintf(cmd, "%s stop", ZKSERVER_CMD);
- CPPUNIT_ASSERT(system(cmd) == 0);
- }
-
- void testServerRequireClientSASL() {
- startServer();
-
- watchctx_t ctx;
- int rc = 0;
- zhandle_t *zk = zookeeper_init(hostPorts, watcher, 10000, 0, &ctx, 0);
- ctx.zh = zk;
- CPPUNIT_ASSERT(zk);
-
- char pathbuf[80];
- struct Stat stat_a = {0};
-
- rc = zoo_create2(zk, "/serverRequireClientSASL", "", 0,
- &ZOO_OPEN_ACL_UNSAFE, 0, pathbuf, sizeof(pathbuf), &stat_a);
- CPPUNIT_ASSERT_EQUAL((int)ZSESSIONCLOSEDREQUIRESASLAUTH, rc);
-
- stopServer();
- }
-};
-
-const char Zookeeper_serverRequireClientSASL::hostPorts[] = "127.0.0.1:22181";
-
-CPPUNIT_TEST_SUITE_REGISTRATION(Zookeeper_serverRequireClientSASL);
diff --git a/zookeeper-client/zookeeper-client-c/tests/zkServer.sh b/zookeeper-client/zookeeper-client-c/tests/zkServer.sh
index 432786ce786..ac2ec1565e9 100755
--- a/zookeeper-client/zookeeper-client-c/tests/zkServer.sh
+++ b/zookeeper-client/zookeeper-client-c/tests/zkServer.sh
@@ -26,7 +26,7 @@ EXTRA_JVM_ARGS=${EXTRA_JVM_ARGS:-""}
if [ "x$1" == "x" ]
then
- echo "USAGE: $0 startClean|start|startCleanReadOnly|startRequireSASLAuth|stop"
+ echo "USAGE: $0 startClean|start|startCleanReadOnly|startRequireSASLAuth [jaasConf]|stop"
exit 2
fi
@@ -127,6 +127,11 @@ PROPERTIES="$EXTRA_JVM_ARGS -Dzookeeper.extendedTypesEnabled=true -Dznode.contai
if [ "x$1" == "xstartRequireSASLAuth" ]
then
PROPERTIES="-Dzookeeper.sessionRequireClientSASLAuth=true $PROPERTIES"
+ if [ "x$2" != "x" ]
+ then
+ PROPERTIES="$PROPERTIES -Dzookeeper.authProvider.1=org.apache.zookeeper.server.auth.SASLAuthenticationProvider"
+ PROPERTIES="$PROPERTIES -Djava.security.auth.login.config=$2"
+ fi
fi
if [ "x$1" == "xstartCleanReadOnly" ]
then