summaryrefslogtreecommitdiff
path: root/zookeeper-client/zookeeper-client-c/src/zk_sasl.c
diff options
context:
space:
mode:
Diffstat (limited to 'zookeeper-client/zookeeper-client-c/src/zk_sasl.c')
-rw-r--r--zookeeper-client/zookeeper-client-c/src/zk_sasl.c541
1 files changed, 541 insertions, 0 deletions
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 000000000..e0ccfb310
--- /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 <sys/socket.h>
+#endif
+
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <zookeeper.h>
+#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;
+ }
+}