From aface2604c53db717299ac3dfe798dfd0c540f99 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Fri, 23 Sep 2016 14:00:10 +0200
Subject: [PATCH 23/36] KCM: request parsing and sending a reply
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Implements parsing the KCM client request into per-client buffers and
sending a response for both the failure case and for success.
The protocol is documented at:
http://k5wiki.kerberos.org/wiki/Projects/KCM_client
Several places don't use the sss_iobuf structure, because they don't
parse variable-length data from the buffer and it's much more efficient
to just allocate the needed request and reply structure on the stack.
Reviewed-by: Michal Židek <mzidek@redhat.com>
Reviewed-by: Simo Sorce <simo@redhat.com>
---
src/responder/kcm/kcmsrv_cmd.c | 467 ++++++++++++++++++++++++++++++++++++++++-
src/responder/kcm/kcmsrv_pvt.h | 21 +-
2 files changed, 474 insertions(+), 14 deletions(-)
diff --git a/src/responder/kcm/kcmsrv_cmd.c b/src/responder/kcm/kcmsrv_cmd.c
index e9a03cbd41169c93e00b0630dc1e05e205881ec9..cbf70353730d8a4e03d8f75c97395f4ef007e77f 100644
--- a/src/responder/kcm/kcmsrv_cmd.c
+++ b/src/responder/kcm/kcmsrv_cmd.c
@@ -19,14 +19,430 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <krb5/krb5.h>
+
#include "config.h"
#include "util/util.h"
+#include "util/sss_iobuf.h"
#include "responder/common/responder.h"
+#include "responder/kcm/kcmsrv_pvt.h"
+#include "responder/kcm/kcm.h"
-struct kcm_proto_ctx {
- void *unused;
+/* The first four bytes of a message is always the size */
+#define KCM_MSG_LEN_SIZE 4
+
+/* The return code is 32bits */
+#define KCM_RETCODE_SIZE 4
+
+/* The maximum length of a request or reply as defined by the RPC
+ * protocol. This is the same constant size as MIT KRB5 uses
+ */
+#define KCM_PACKET_MAX_SIZE 2048
+
+/* KCM operation, its raw input and raw output and result */
+struct kcm_op_io {
+ struct kcm_op *op;
+ struct kcm_data request;
+ struct sss_iobuf *reply;
+};
+
+/**
+ * KCM IO-vector operations
+ */
+struct kcm_iovec {
+ /* We don't use iovec b/c void pointers don't allow for
+ * pointer arithmetics and it's convenient to keep track
+ * of processed bytes
+ */
+ uint8_t *kiov_base;
+ size_t kiov_len;
+ size_t nprocessed;
+};
+
+static errno_t kcm_iovec_op(int fd, struct kcm_iovec *kiov, bool do_read)
+{
+ ssize_t len;
+ struct iovec iov[1];
+
+ iov[0].iov_base = kiov->kiov_base + kiov->nprocessed;
+ iov[0].iov_len = kiov->kiov_len - kiov->nprocessed;
+ if (iov[0].iov_len == 0) {
+ /* This iovec is full (read) or depleted (write), proceed to the next one */
+ return EOK;
+ }
+
+ if (do_read) {
+ len = readv(fd, iov, 1);
+ } else {
+ len = writev(fd, iov, 1);
+ }
+
+ if (len == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
+ return EAGAIN;
+ } else {
+ return errno;
+ }
+ }
+
+ if (len == 0) {
+ /* Read event on fd that doesn't yield data? error */
+ return ENODATA;
+ }
+
+ /* Decrease the amount of available free space in the iovec */
+ kiov->nprocessed += len;
+ return EOK;
+}
+
+static errno_t kcm_read_iovec(int fd, struct kcm_iovec *kiov)
+{
+ return kcm_iovec_op(fd, kiov, true);
+}
+
+static errno_t kcm_write_iovec(int fd, struct kcm_iovec *kiov)
+{
+ return kcm_iovec_op(fd, kiov, false);
+}
+
+/**
+ * Parsing KCM input
+ *
+ * The request is received as two IO vectors:
+ *
+ * first iovec:
+ * length 32-bit big-endian integer
+ *
+ * second iovec:
+ * major protocol number 8-bit big-endian integer
+ * minor protocol number 8-bit big-endian integer
+ * opcode 16-bit big-endian integer
+ * message payload buffer
+ */
+struct kcm_reqbuf {
+ uint8_t lenbuf[KCM_MSG_LEN_SIZE];
+ struct kcm_iovec v_len;
+
+ /* Includes the major, minor versions etc */
+ uint8_t msgbuf[KCM_PACKET_MAX_SIZE];
+ struct kcm_iovec v_msg;
+};
+
+static errno_t kcm_input_parse(struct kcm_reqbuf *reqbuf,
+ struct kcm_op_io *op_io)
+{
+ size_t lc = 0;
+ size_t mc = 0;
+ uint16_t opcode = 0;
+ uint16_t opcode_be = 0;
+ uint32_t len_be = 0;
+ uint32_t msglen;
+ uint8_t proto_maj = 0;
+ uint8_t proto_min = 0;
+
+ /* The first 4 bytes before the payload is message length */
+ SAFEALIGN_COPY_UINT32_CHECK(&len_be,
+ reqbuf->v_len.kiov_base,
+ reqbuf->v_len.kiov_len,
+ &lc);
+ msglen = be32toh(len_be);
+ DEBUG(SSSDBG_TRACE_LIBS,
+ "Received message with length %"PRIu32"\n", msglen);
+
+ if (msglen == 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Illegal zero-length message\n");
+ return EBADMSG;
+ }
+
+ if (msglen != reqbuf->v_msg.nprocessed) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Sender claims the message is %"PRIu32" bytes, "
+ "but received %zu\n",
+ msglen, reqbuf->v_msg.nprocessed);
+ return EBADMSG;
+ }
+
+ /* First 16 bits are 8 bit major and 8bit major protocol version */
+ SAFEALIGN_COPY_UINT8_CHECK(&proto_maj,
+ reqbuf->v_msg.kiov_base + mc,
+ reqbuf->v_msg.kiov_len,
+ &mc);
+ SAFEALIGN_COPY_UINT8_CHECK(&proto_min,
+ reqbuf->v_msg.kiov_base + mc,
+ reqbuf->v_msg.kiov_len,
+ &mc);
+
+ if (proto_maj != KCM_PROTOCOL_VERSION_MAJOR) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Expected major version %d, got %"PRIu16"\n",
+ KCM_PROTOCOL_VERSION_MAJOR, (uint16_t) proto_maj);
+ return ERR_KCM_MALFORMED_IN_PKT;
+ }
+
+ if (proto_min != KCM_PROTOCOL_VERSION_MINOR) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Expected minor version %d, got %"PRIu16"\n",
+ KCM_PROTOCOL_VERSION_MINOR, (uint16_t) proto_maj);
+ return ERR_KCM_MALFORMED_IN_PKT;
+ }
+
+ SAFEALIGN_COPY_UINT16_CHECK(&opcode_be,
+ reqbuf->v_msg.kiov_base + mc,
+ reqbuf->v_msg.kiov_len,
+ &mc);
+
+ opcode = be16toh(opcode_be);
+ DEBUG(SSSDBG_TRACE_LIBS, "Received operation code %"PRIu16"\n", opcode);
+
+ return EOK;
+}
+
+/**
+ * Constructing a reply for failure and success
+ *
+ * The reply consists of three IO vectors:
+ * 1) length iovec:
+ * length: 32-bit big-endian
+ *
+ * 2) return code iovec:
+ * retcode: 32-bit big-endian. Non-zero on failure in the KCM server,
+ * zero if the KCM operation ran (even if the operation itself
+ * failed)
+ *
+ * 3) reply iovec
+ * message: buffer, first 32-bits of the buffer is the return code of
+ * the KCM operation, the rest depends on the operation itself.
+ * The buffer's length is specified by the first integer in the
+ * reply (very intuitive, right?)
+ *
+ * The client always reads the length and return code iovectors. However, the
+ * client reads the reply iovec only if retcode is 0 in the return code iovector
+ * (see kcmio_unix_socket_read() in the MIT tree)
+ */
+struct kcm_repbuf {
+ uint8_t lenbuf[KCM_MSG_LEN_SIZE];
+ struct kcm_iovec v_len;
+
+ uint8_t rcbuf[KCM_RETCODE_SIZE];
+ struct kcm_iovec v_rc;
+
+ uint8_t msgbuf[KCM_PACKET_MAX_SIZE];
+ struct kcm_iovec v_msg;
+};
+
+static errno_t kcm_failbuf_construct(errno_t ret,
+ struct kcm_repbuf *repbuf)
+{
+ size_t c;
+
+ c = 0;
+ SAFEALIGN_SETMEM_UINT32(repbuf->lenbuf, 0, &c);
+ c = 0;
+ SAFEALIGN_SETMEM_UINT32(repbuf->rcbuf, htobe32(ret), &c);
+
+ return EOK;
+}
+
+/**
+ * Construct a reply buffer and send it to the KCM client
+ */
+static void kcm_reply_error(struct cli_ctx *cctx,
+ errno_t retcode,
+ struct kcm_repbuf *repbuf)
+{
+ errno_t ret;
+ krb5_error_code kerr;
+
+ DEBUG(SSSDBG_OP_FAILURE,
+ "KCM operation returs failure [%d]: %s\n",
+ retcode, sss_strerror(retcode));
+ kerr = sss2krb5_error(retcode);
+
+ ret = kcm_failbuf_construct(kerr, repbuf);
+ if (ret != EOK) {
+ /* If we can't construct the reply buffer, just terminate the client */
+ talloc_free(cctx);
+ return;
+ }
+
+ TEVENT_FD_WRITEABLE(cctx->cfde);
+}
+
+/**
+ * Request-reply dispatcher
+ */
+struct kcm_req_ctx {
+ /* client context owns per-client buffers including this one */
+ struct cli_ctx *cctx;
+
+ /* raw IO buffers */
+ struct kcm_reqbuf reqbuf;
+ struct kcm_repbuf repbuf;
+
+ /* long-lived responder structures */
+ struct kcm_ctx *kctx;
+
+ struct kcm_op_io op_io;
};
+static errno_t kcm_recv_data(int fd, struct kcm_reqbuf *reqbuf)
+{
+ errno_t ret;
+
+ ret = kcm_read_iovec(fd, &reqbuf->v_len);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ ret = kcm_read_iovec(fd, &reqbuf->v_msg);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ return EOK;
+}
+
+static struct kcm_req_ctx *kcm_new_req(TALLOC_CTX *mem_ctx,
+ struct cli_ctx *cctx,
+ struct kcm_ctx *kctx)
+{
+ struct kcm_req_ctx *req;
+
+ req = talloc_zero(cctx, struct kcm_req_ctx);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ req->reqbuf.v_len.kiov_base = req->reqbuf.lenbuf;
+ req->reqbuf.v_len.kiov_len = KCM_MSG_LEN_SIZE;
+
+ req->reqbuf.v_msg.kiov_base = req->reqbuf.msgbuf;
+ req->reqbuf.v_msg.kiov_len = KCM_PACKET_MAX_SIZE;
+
+ req->repbuf.v_len.kiov_base = req->repbuf.lenbuf;
+ req->repbuf.v_len.kiov_len = KCM_MSG_LEN_SIZE;
+
+ req->repbuf.v_rc.kiov_base = req->repbuf.rcbuf;
+ req->repbuf.v_rc.kiov_len = KCM_RETCODE_SIZE;
+
+ req->repbuf.v_msg.kiov_base = req->repbuf.msgbuf;
+ /* Length of the msg iobuf will be adjusted later, so far use the full
+ * length so that constructing the reply can use that capacity
+ */
+ req->repbuf.v_msg.kiov_len = KCM_PACKET_MAX_SIZE;
+
+ req->cctx = cctx;
+ req->kctx = kctx;
+
+ return req;
+}
+
+static void kcm_recv(struct cli_ctx *cctx)
+{
+ struct kcm_req_ctx *req;
+ struct kcm_ctx *kctx;
+ int ret;
+
+ kctx = talloc_get_type(cctx->rctx->pvt_ctx, struct kcm_ctx);
+ req = talloc_get_type(cctx->state_ctx, struct kcm_req_ctx);
+ if (req == NULL) {
+ /* A new request comes in, setup data structures */
+ req = kcm_new_req(cctx, cctx, kctx);
+ if (req == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Cannot set up client connection\n");
+ talloc_free(cctx);
+ return;
+ }
+
+ cctx->state_ctx = req;
+ }
+
+ ret = kcm_recv_data(cctx->cfd, &req->reqbuf);
+ switch (ret) {
+ case ENODATA:
+ DEBUG(SSSDBG_TRACE_ALL, "Client closed connection.\n");
+ talloc_free(cctx);
+ return;
+ case EAGAIN:
+ DEBUG(SSSDBG_TRACE_ALL, "Retry later\n");
+ return;
+ case EOK:
+ /* all fine */
+ break;
+ default:
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Failed to receive data (%d, %s), aborting client\n",
+ ret, sss_strerror(ret));
+ talloc_free(cctx);
+ return;
+ }
+
+ ret = kcm_input_parse(&req->reqbuf, &req->op_io);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Failed to parse data (%d, %s), aborting client\n",
+ ret, sss_strerror(ret));
+ goto fail;
+ }
+
+ /* do not read anymore, client is done sending */
+ TEVENT_FD_NOT_READABLE(cctx->cfde);
+
+ kcm_reply_error(cctx, ret, &req->repbuf);
+ return;
+
+fail:
+ /* Fail with reply */
+ kcm_reply_error(cctx, ret, &req->repbuf);
+}
+
+static int kcm_send_data(struct cli_ctx *cctx)
+{
+ struct kcm_req_ctx *req;
+ errno_t ret;
+
+ req = talloc_get_type(cctx->state_ctx, struct kcm_req_ctx);
+
+ ret = kcm_write_iovec(cctx->cfd, &req->repbuf.v_len);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ ret = kcm_write_iovec(cctx->cfd, &req->repbuf.v_rc);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ ret = kcm_write_iovec(cctx->cfd, &req->repbuf.v_msg);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ return EOK;
+}
+
+static void kcm_send(struct cli_ctx *cctx)
+{
+ errno_t ret;
+
+ ret = kcm_send_data(cctx);
+ if (ret == EAGAIN) {
+ /* not all data was sent, loop again */
+ return;
+ } else if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Failed to send data, aborting client!\n");
+ talloc_free(cctx);
+ return;
+ }
+
+ /* ok all sent */
+ TEVENT_FD_NOT_WRITEABLE(cctx->cfde);
+ TEVENT_FD_READABLE(cctx->cfde);
+ talloc_zfree(cctx->state_ctx);
+ return;
+}
+
static void kcm_fd_handler(struct tevent_context *ev,
struct tevent_fd *fde,
uint16_t flags, void *ptr)
@@ -39,25 +455,54 @@ static void kcm_fd_handler(struct tevent_context *ev,
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Could not create idle timer for client. "
- "This connection may not auto-terminate\n");
+ "This connection may not auto-terminate\n");
/* Non-fatal, continue */
}
+
+ if (flags & TEVENT_FD_READ) {
+ kcm_recv(cctx);
+ return;
+ }
+ if (flags & TEVENT_FD_WRITE) {
+ kcm_send(cctx);
+ return;
+ }
}
int kcm_connection_setup(struct cli_ctx *cctx)
{
- struct kcm_proto_ctx *protocol_ctx;
-
- protocol_ctx = talloc_zero(cctx, struct kcm_proto_ctx);
- if (protocol_ctx == NULL) {
- return ENOMEM;
- }
-
- cctx->protocol_ctx = protocol_ctx;
cctx->cfd_handler = kcm_fd_handler;
return EOK;
}
+krb5_error_code sss2krb5_error(errno_t err)
+{
+ switch (err) {
+ case EOK:
+ return 0;
+ case ENOMEM:
+ return KRB5_CC_NOMEM;
+ case EACCES:
+ return KRB5_FCC_PERM;
+ case ERR_KCM_OP_NOT_IMPLEMENTED:
+ return KRB5_CC_NOSUPP;
+ case ERR_WRONG_NAME_FORMAT:
+ return KRB5_CC_BADNAME;
+ case ERR_NO_MATCHING_CREDS:
+ return KRB5_FCC_NOFILE;
+ case ERR_NO_CREDS:
+ return KRB5_CC_NOTFOUND;
+ case ERR_KCM_CC_END:
+ return KRB5_CC_END;
+ case ERR_KCM_MALFORMED_IN_PKT:
+ case EINVAL:
+ case EIO:
+ return KRB5_CC_IO;
+ }
+
+ return KRB5_FCC_INTERNAL;
+}
+
/* Dummy, not used here but required to link to other responder files */
struct cli_protocol_version *register_cli_protocol_version(void)
{
diff --git a/src/responder/kcm/kcmsrv_pvt.h b/src/responder/kcm/kcmsrv_pvt.h
index a7c9d062c17f09986d894064176c3a461d396ac0..fd1fd9fa32d59a323d465def68999f24f84e3923 100644
--- a/src/responder/kcm/kcmsrv_pvt.h
+++ b/src/responder/kcm/kcmsrv_pvt.h
@@ -27,13 +27,20 @@
#include <sys/types.h>
#include "responder/common/responder.h"
-/* KCM IO structure */
+/*
+ * KCM IO structure
+ *
+ * In theory we cold use sss_iobuf there, but since iobuf was
+ * made opaque, this allows it to allocate the structures on
+ * the stack in one go.
+ * */
struct kcm_data {
uint8_t *data;
size_t length;
};
-/* To avoid leaking the sssd-specific responder data to other
+/*
+ * To avoid leaking the sssd-specific responder data to other
* modules, the ccache databases and other KCM specific data
* are kept separately
*/
@@ -41,7 +48,8 @@ struct kcm_resp_ctx {
krb5_context k5c;
};
-/* responder context that contains both the responder data,
+/*
+ * responder context that contains both the responder data,
* like the ccaches and the sssd-specific stuff like the
* generic responder ctx
*/
@@ -55,4 +63,11 @@ struct kcm_ctx {
int kcm_connection_setup(struct cli_ctx *cctx);
+/*
+ * Internally in SSSD-KCM we use SSSD-internal error codes so that we
+ * can always the same sss_strerror() functions to format the errors
+ * nicely, but the client expects libkrb5 error codes.
+ */
+krb5_error_code sss2krb5_error(errno_t err);
+
#endif /* __KCMSRV_PVT_H__ */
--
2.9.3