summaryrefslogtreecommitdiffstats
path: root/third_party/heimdal/kcm/protocol.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
commit8daa83a594a2e98f39d764422bfbdbc62c9efd44 (patch)
tree4099e8021376c7d8c05bdf8503093d80e9c7bad0 /third_party/heimdal/kcm/protocol.c
parentInitial commit. (diff)
downloadsamba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.tar.xz
samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.zip
Adding upstream version 2:4.20.0+dfsg.upstream/2%4.20.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/heimdal/kcm/protocol.c')
-rw-r--r--third_party/heimdal/kcm/protocol.c1812
1 files changed, 1812 insertions, 0 deletions
diff --git a/third_party/heimdal/kcm/protocol.c b/third_party/heimdal/kcm/protocol.c
new file mode 100644
index 0000000..b5442e4
--- /dev/null
+++ b/third_party/heimdal/kcm/protocol.c
@@ -0,0 +1,1812 @@
+/*
+ * Copyright (c) 2005, PADL Software Pty Ltd.
+ * All rights reserved.
+ *
+ * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of PADL Software nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY PADL SOFTWARE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL PADL SOFTWARE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "kcm_locl.h"
+#include <heimntlm.h>
+
+static void
+kcm_drop_default_cache(krb5_context context, kcm_client *client, char *name);
+
+
+int
+kcm_is_same_session(kcm_client *client, uid_t uid, pid_t session)
+{
+#if 0 /* XXX pppd is running in diffrent session the user */
+ if (session != -1)
+ return (client->session == session);
+ else
+#endif
+ return (client->uid == uid);
+}
+
+static krb5_error_code
+kcm_op_noop(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+{
+ KCM_LOG_REQUEST(context, client, opcode);
+
+ return 0;
+}
+
+/*
+ * Request:
+ * NameZ
+ * Response:
+ * NameZ
+ *
+ */
+static krb5_error_code
+kcm_op_get_name(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+
+{
+ krb5_error_code ret;
+ char *name = NULL;
+ kcm_ccache ccache;
+
+ ret = krb5_ret_stringz(request, &name);
+ if (ret)
+ return ret;
+
+ KCM_LOG_REQUEST_NAME(context, client, opcode, name);
+
+ ret = kcm_ccache_resolve_client(context, client, opcode,
+ name, &ccache);
+ if (ret) {
+ free(name);
+ return ret;
+ }
+
+ ret = krb5_store_stringz(response, ccache->name);
+ if (ret) {
+ kcm_release_ccache(context, ccache);
+ free(name);
+ return ret;
+ }
+
+ free(name);
+ kcm_release_ccache(context, ccache);
+ return 0;
+}
+
+/*
+ * Request:
+ *
+ * Response:
+ * NameZ
+ */
+static krb5_error_code
+kcm_op_gen_new(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+{
+ krb5_error_code ret;
+ char *name;
+
+ KCM_LOG_REQUEST(context, client, opcode);
+
+ name = kcm_ccache_nextid(client->pid, client->uid, client->gid);
+ if (name == NULL) {
+ return KRB5_CC_NOMEM;
+ }
+
+ ret = krb5_store_stringz(response, name);
+ free(name);
+
+ return ret;
+}
+
+/*
+ * Request:
+ * NameZ
+ * Principal
+ *
+ * Response:
+ *
+ */
+static krb5_error_code
+kcm_op_initialize(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+{
+ kcm_ccache ccache;
+ krb5_principal principal;
+ krb5_error_code ret;
+ char *name;
+#if 0
+ kcm_event event;
+#endif
+
+ KCM_LOG_REQUEST(context, client, opcode);
+
+ ret = krb5_ret_stringz(request, &name);
+ if (ret)
+ return ret;
+
+ ret = krb5_ret_principal(request, &principal);
+ if (ret) {
+ free(name);
+ return ret;
+ }
+
+ ret = kcm_ccache_new_client(context, client, name, &ccache);
+ if (ret) {
+ free(name);
+ krb5_free_principal(context, principal);
+ return ret;
+ }
+
+ ccache->client = principal;
+
+ free(name);
+
+#if 0
+ /*
+ * Create a new credentials cache. To mitigate DoS attacks we will
+ * expire it in 30 minutes unless it has some credentials added
+ * to it
+ */
+
+ event.fire_time = 30 * 60;
+ event.expire_time = 0;
+ event.backoff_time = 0;
+ event.action = KCM_EVENT_DESTROY_EMPTY_CACHE;
+ event.ccache = ccache;
+
+ ret = kcm_enqueue_event_relative(context, &event);
+#endif
+
+ kcm_release_ccache(context, ccache);
+
+ return ret;
+}
+
+/*
+ * Request:
+ * NameZ
+ *
+ * Response:
+ *
+ */
+static krb5_error_code
+kcm_op_destroy(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+{
+ krb5_error_code ret;
+ char *name;
+
+ ret = krb5_ret_stringz(request, &name);
+ if (ret)
+ return ret;
+
+ KCM_LOG_REQUEST_NAME(context, client, opcode, name);
+
+ ret = kcm_ccache_destroy_client(context, client, name);
+ if (ret == 0)
+ kcm_drop_default_cache(context, client, name);
+
+ free(name);
+
+ return ret;
+}
+
+/*
+ * Request:
+ * NameZ
+ * Creds
+ *
+ * Response:
+ *
+ */
+static krb5_error_code
+kcm_op_store(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+{
+ krb5_creds creds;
+ krb5_error_code ret;
+ kcm_ccache ccache;
+ char *name;
+
+ ret = krb5_ret_stringz(request, &name);
+ if (ret)
+ return ret;
+
+ KCM_LOG_REQUEST_NAME(context, client, opcode, name);
+
+ ret = krb5_ret_creds(request, &creds);
+ if (ret) {
+ free(name);
+ return ret;
+ }
+
+ ret = kcm_ccache_resolve_client(context, client, opcode,
+ name, &ccache);
+ if (ret) {
+ free(name);
+ krb5_free_cred_contents(context, &creds);
+ return ret;
+ }
+
+ ret = kcm_ccache_store_cred(context, ccache, &creds, 0);
+ if (ret) {
+ free(name);
+ krb5_free_cred_contents(context, &creds);
+ kcm_release_ccache(context, ccache);
+ return ret;
+ }
+
+ kcm_ccache_enqueue_default(context, ccache, &creds);
+
+ free(name);
+ kcm_release_ccache(context, ccache);
+
+ return 0;
+}
+
+/*
+ * Request:
+ * NameZ
+ * WhichFields
+ * MatchCreds
+ *
+ * Response:
+ * Creds
+ *
+ */
+static krb5_error_code
+kcm_op_retrieve(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+{
+ uint32_t flags;
+ krb5_creds mcreds;
+ krb5_error_code ret;
+ kcm_ccache ccache;
+ char *name;
+ krb5_creds *credp;
+ int free_creds = 0;
+
+ ret = krb5_ret_stringz(request, &name);
+ if (ret)
+ return ret;
+
+ KCM_LOG_REQUEST_NAME(context, client, opcode, name);
+
+ ret = krb5_ret_uint32(request, &flags);
+ if (ret) {
+ free(name);
+ return ret;
+ }
+
+ ret = krb5_ret_creds_tag(request, &mcreds);
+ if (ret) {
+ free(name);
+ return ret;
+ }
+
+ if (disallow_getting_krbtgt && krb5_principal_is_krbtgt(context, mcreds.server))
+ {
+ free(name);
+ krb5_free_cred_contents(context, &mcreds);
+ return KRB5_FCC_PERM;
+ }
+
+ ret = kcm_ccache_resolve_client(context, client, opcode,
+ name, &ccache);
+ if (ret) {
+ free(name);
+ krb5_free_cred_contents(context, &mcreds);
+ return ret;
+ }
+
+ ret = kcm_ccache_retrieve_cred(context, ccache, flags,
+ &mcreds, &credp);
+ if (ret && ((flags & KRB5_GC_CACHED) == 0) &&
+ !krb5_is_config_principal(context, mcreds.server)) {
+ krb5_ccache_data ccdata;
+
+ /* try and acquire */
+ HEIMDAL_MUTEX_lock(&ccache->mutex);
+
+ /* Fake up an internal ccache */
+ kcm_internal_ccache(context, ccache, &ccdata);
+
+ /* glue cc layer will store creds */
+ ret = krb5_get_credentials(context, 0, &ccdata, &mcreds, &credp);
+ if (ret == 0)
+ free_creds = 1;
+
+ HEIMDAL_MUTEX_unlock(&ccache->mutex);
+ }
+
+ if (ret == 0) {
+ ret = krb5_store_creds(response, credp);
+ }
+
+ free(name);
+ krb5_free_cred_contents(context, &mcreds);
+ kcm_release_ccache(context, ccache);
+
+ if (free_creds)
+ krb5_free_cred_contents(context, credp);
+
+ return ret;
+}
+
+/*
+ * Request:
+ * NameZ
+ *
+ * Response:
+ * Principal
+ */
+static krb5_error_code
+kcm_op_get_principal(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+{
+ krb5_error_code ret;
+ kcm_ccache ccache;
+ char *name;
+
+ ret = krb5_ret_stringz(request, &name);
+ if (ret)
+ return ret;
+
+ KCM_LOG_REQUEST_NAME(context, client, opcode, name);
+
+ ret = kcm_ccache_resolve_client(context, client, opcode,
+ name, &ccache);
+ if (ret) {
+ free(name);
+ return ret;
+ }
+
+ if (ccache->client == NULL)
+ ret = KRB5_CC_NOTFOUND;
+ else
+ ret = krb5_store_principal(response, ccache->client);
+
+ free(name);
+ kcm_release_ccache(context, ccache);
+
+ return ret;
+}
+
+/*
+ * Request:
+ * NameZ
+ *
+ * Response:
+ * UUIDs
+ *
+ */
+static krb5_error_code
+kcm_op_get_cred_uuid_list(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+{
+ struct kcm_creds *creds;
+ krb5_error_code ret;
+ kcm_ccache ccache;
+ char *name;
+
+ ret = krb5_ret_stringz(request, &name);
+ if (ret)
+ return ret;
+
+ KCM_LOG_REQUEST_NAME(context, client, opcode, name);
+
+ ret = kcm_ccache_resolve_client(context, client, opcode,
+ name, &ccache);
+ free(name);
+ if (ret)
+ return ret;
+
+ for (creds = ccache->creds ; creds ; creds = creds->next) {
+ ssize_t sret;
+ sret = krb5_storage_write(response, &creds->uuid, sizeof(creds->uuid));
+ if (sret != sizeof(creds->uuid)) {
+ ret = ENOMEM;
+ break;
+ }
+ }
+
+ kcm_release_ccache(context, ccache);
+
+ return ret;
+}
+
+/*
+ * Request:
+ * NameZ
+ * Cursor
+ *
+ * Response:
+ * Creds
+ */
+static krb5_error_code
+kcm_op_get_cred_by_uuid(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+{
+ krb5_error_code ret;
+ kcm_ccache ccache;
+ char *name;
+ struct kcm_creds *c;
+ kcmuuid_t uuid;
+ ssize_t sret;
+
+ ret = krb5_ret_stringz(request, &name);
+ if (ret)
+ return ret;
+
+ KCM_LOG_REQUEST_NAME(context, client, opcode, name);
+
+ ret = kcm_ccache_resolve_client(context, client, opcode,
+ name, &ccache);
+ free(name);
+ if (ret)
+ return ret;
+
+ sret = krb5_storage_read(request, &uuid, sizeof(uuid));
+ if (sret != sizeof(uuid)) {
+ kcm_release_ccache(context, ccache);
+ krb5_clear_error_message(context);
+ return KRB5_CC_IO;
+ }
+
+ c = kcm_ccache_find_cred_uuid(context, ccache, uuid);
+ if (c == NULL) {
+ kcm_release_ccache(context, ccache);
+ return KRB5_CC_END;
+ }
+
+ HEIMDAL_MUTEX_lock(&ccache->mutex);
+ ret = krb5_store_creds(response, &c->cred);
+ HEIMDAL_MUTEX_unlock(&ccache->mutex);
+
+ kcm_release_ccache(context, ccache);
+
+ return ret;
+}
+
+/*
+ * Request:
+ * NameZ
+ * WhichFields
+ * MatchCreds
+ *
+ * Response:
+ *
+ */
+static krb5_error_code
+kcm_op_remove_cred(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+{
+ uint32_t whichfields;
+ krb5_creds mcreds;
+ krb5_error_code ret;
+ kcm_ccache ccache;
+ char *name;
+
+ ret = krb5_ret_stringz(request, &name);
+ if (ret)
+ return ret;
+
+ KCM_LOG_REQUEST_NAME(context, client, opcode, name);
+
+ ret = krb5_ret_uint32(request, &whichfields);
+ if (ret) {
+ free(name);
+ return ret;
+ }
+
+ ret = krb5_ret_creds_tag(request, &mcreds);
+ if (ret) {
+ free(name);
+ return ret;
+ }
+
+ ret = kcm_ccache_resolve_client(context, client, opcode,
+ name, &ccache);
+ if (ret) {
+ free(name);
+ krb5_free_cred_contents(context, &mcreds);
+ return ret;
+ }
+
+ ret = kcm_ccache_remove_cred(context, ccache, whichfields, &mcreds);
+
+ /* XXX need to remove any events that match */
+
+ free(name);
+ krb5_free_cred_contents(context, &mcreds);
+ kcm_release_ccache(context, ccache);
+
+ return ret;
+}
+
+/*
+ * Request:
+ * NameZ
+ * Flags
+ *
+ * Response:
+ *
+ */
+static krb5_error_code
+kcm_op_set_flags(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+{
+ uint32_t flags;
+ krb5_error_code ret;
+ kcm_ccache ccache;
+ char *name;
+
+ ret = krb5_ret_stringz(request, &name);
+ if (ret)
+ return ret;
+
+ KCM_LOG_REQUEST_NAME(context, client, opcode, name);
+
+ ret = krb5_ret_uint32(request, &flags);
+ if (ret) {
+ free(name);
+ return ret;
+ }
+
+ ret = kcm_ccache_resolve_client(context, client, opcode,
+ name, &ccache);
+ if (ret) {
+ free(name);
+ return ret;
+ }
+
+ /* we don't really support any flags yet */
+ free(name);
+ kcm_release_ccache(context, ccache);
+
+ return 0;
+}
+
+/*
+ * Request:
+ * NameZ
+ * UID
+ * GID
+ *
+ * Response:
+ *
+ */
+static krb5_error_code
+kcm_op_chown(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+{
+ uint32_t uid;
+ uint32_t gid;
+ krb5_error_code ret;
+ kcm_ccache ccache;
+ char *name;
+
+ ret = krb5_ret_stringz(request, &name);
+ if (ret)
+ return ret;
+
+ KCM_LOG_REQUEST_NAME(context, client, opcode, name);
+
+ ret = krb5_ret_uint32(request, &uid);
+ if (ret) {
+ free(name);
+ return ret;
+ }
+
+ ret = krb5_ret_uint32(request, &gid);
+ if (ret) {
+ free(name);
+ return ret;
+ }
+
+ ret = kcm_ccache_resolve_client(context, client, opcode,
+ name, &ccache);
+ if (ret) {
+ free(name);
+ return ret;
+ }
+
+ ret = kcm_chown(context, client, ccache, uid, gid);
+
+ free(name);
+ kcm_release_ccache(context, ccache);
+
+ return ret;
+}
+
+/*
+ * Request:
+ * NameZ
+ * Mode
+ *
+ * Response:
+ *
+ */
+static krb5_error_code
+kcm_op_chmod(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+{
+ uint16_t mode;
+ krb5_error_code ret;
+ kcm_ccache ccache;
+ char *name;
+
+ ret = krb5_ret_stringz(request, &name);
+ if (ret)
+ return ret;
+
+ KCM_LOG_REQUEST_NAME(context, client, opcode, name);
+
+ ret = krb5_ret_uint16(request, &mode);
+ if (ret) {
+ free(name);
+ return ret;
+ }
+
+ ret = kcm_ccache_resolve_client(context, client, opcode,
+ name, &ccache);
+ if (ret) {
+ free(name);
+ return ret;
+ }
+
+ ret = kcm_chmod(context, client, ccache, mode);
+
+ free(name);
+ kcm_release_ccache(context, ccache);
+
+ return ret;
+}
+
+/*
+ * Protocol extensions for moving ticket acquisition responsibility
+ * from client to KCM follow.
+ */
+
+/*
+ * Request:
+ * NameZ
+ * ServerPrincipalPresent
+ * ServerPrincipal OPTIONAL
+ * Key
+ *
+ * Repsonse:
+ *
+ */
+static krb5_error_code
+kcm_op_get_initial_ticket(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+{
+ krb5_error_code ret;
+ kcm_ccache ccache;
+ char *name;
+ int8_t not_tgt = 0;
+ krb5_principal server = NULL;
+ krb5_keyblock key;
+
+ krb5_keyblock_zero(&key);
+
+ ret = krb5_ret_stringz(request, &name);
+ if (ret)
+ return ret;
+
+ KCM_LOG_REQUEST_NAME(context, client, opcode, name);
+
+ ret = krb5_ret_int8(request, &not_tgt);
+ if (ret) {
+ free(name);
+ return ret;
+ }
+
+ if (not_tgt) {
+ ret = krb5_ret_principal(request, &server);
+ if (ret) {
+ free(name);
+ return ret;
+ }
+ }
+
+ ret = krb5_ret_keyblock(request, &key);
+ if (ret) {
+ free(name);
+ if (server != NULL)
+ krb5_free_principal(context, server);
+ return ret;
+ }
+
+ ret = kcm_ccache_resolve_client(context, client, opcode,
+ name, &ccache);
+ if (ret == 0) {
+ HEIMDAL_MUTEX_lock(&ccache->mutex);
+
+ if (ccache->server != NULL) {
+ krb5_free_principal(context, ccache->server);
+ ccache->server = NULL;
+ }
+
+ krb5_free_keyblock(context, &ccache->key.keyblock);
+
+ ccache->server = server;
+ ccache->key.keyblock = key;
+ ccache->flags |= KCM_FLAGS_USE_CACHED_KEY;
+
+ ret = kcm_ccache_enqueue_default(context, ccache, NULL);
+ if (ret) {
+ ccache->server = NULL;
+ krb5_keyblock_zero(&ccache->key.keyblock);
+ ccache->flags &= ~(KCM_FLAGS_USE_CACHED_KEY);
+ }
+
+ HEIMDAL_MUTEX_unlock(&ccache->mutex);
+ }
+
+ free(name);
+
+ if (ret != 0) {
+ krb5_free_principal(context, server);
+ krb5_free_keyblock_contents(context, &key);
+ }
+
+ kcm_release_ccache(context, ccache);
+
+ return ret;
+}
+
+/*
+ * Request:
+ * NameZ
+ * ServerPrincipal
+ * KDCFlags
+ * EncryptionType
+ *
+ * Repsonse:
+ *
+ */
+static krb5_error_code
+kcm_op_get_ticket(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+{
+ krb5_error_code ret;
+ kcm_ccache ccache;
+ char *name;
+ krb5_principal server = NULL;
+ krb5_ccache_data ccdata;
+ krb5_creds in, *out;
+ krb5_kdc_flags flags;
+
+ memset(&in, 0, sizeof(in));
+
+ ret = krb5_ret_stringz(request, &name);
+ if (ret)
+ return ret;
+
+ KCM_LOG_REQUEST_NAME(context, client, opcode, name);
+
+ ret = krb5_ret_uint32(request, &flags.i);
+ if (ret) {
+ free(name);
+ return ret;
+ }
+
+ ret = krb5_ret_int32(request, &in.session.keytype);
+ if (ret) {
+ free(name);
+ return ret;
+ }
+
+ ret = krb5_ret_principal(request, &server);
+ if (ret) {
+ free(name);
+ return ret;
+ }
+
+ ret = kcm_ccache_resolve_client(context, client, opcode,
+ name, &ccache);
+ if (ret) {
+ krb5_free_principal(context, server);
+ free(name);
+ return ret;
+ }
+
+ HEIMDAL_MUTEX_lock(&ccache->mutex);
+
+ /* Fake up an internal ccache */
+ kcm_internal_ccache(context, ccache, &ccdata);
+
+ in.client = ccache->client;
+ in.server = server;
+ in.times.endtime = 0;
+
+ /* glue cc layer will store creds */
+ ret = krb5_get_credentials_with_flags(context, 0, flags,
+ &ccdata, &in, &out);
+
+ HEIMDAL_MUTEX_unlock(&ccache->mutex);
+
+ krb5_free_principal(context, server);
+
+ if (ret == 0)
+ krb5_free_cred_contents(context, out);
+
+ kcm_release_ccache(context, ccache);
+ free(name);
+
+ return ret;
+}
+
+/*
+ * Request:
+ * OldNameZ
+ * NewNameZ
+ *
+ * Repsonse:
+ *
+ */
+static krb5_error_code
+kcm_op_move_cache(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+{
+ krb5_error_code ret;
+ kcm_ccache oldid, newid;
+ char *oldname, *newname;
+
+ ret = krb5_ret_stringz(request, &oldname);
+ if (ret)
+ return ret;
+
+ KCM_LOG_REQUEST_NAME(context, client, opcode, oldname);
+
+ ret = krb5_ret_stringz(request, &newname);
+ if (ret) {
+ free(oldname);
+ return ret;
+ }
+
+ /* move to ourself is simple, done! */
+ if (strcmp(oldname, newname) == 0) {
+ free(oldname);
+ free(newname);
+ return 0;
+ }
+
+ ret = kcm_ccache_resolve_client(context, client, opcode, oldname, &oldid);
+ if (ret) {
+ free(oldname);
+ free(newname);
+ return ret;
+ }
+
+ /* Check if new credential cache exists, if not create one. */
+ ret = kcm_ccache_resolve_client(context, client, opcode, newname, &newid);
+ if (ret == KRB5_FCC_NOFILE)
+ ret = kcm_ccache_new_client(context, client, newname, &newid);
+ free(newname);
+
+ if (ret) {
+ free(oldname);
+ kcm_release_ccache(context, oldid);
+ return ret;
+ }
+
+ HEIMDAL_MUTEX_lock(&oldid->mutex);
+ HEIMDAL_MUTEX_lock(&newid->mutex);
+
+ /* move content */
+ {
+ kcm_ccache_data tmp;
+
+#define MOVE(n,o,f) { tmp.f = n->f ; n->f = o->f; o->f = tmp.f; }
+
+ MOVE(newid, oldid, flags);
+ MOVE(newid, oldid, client);
+ MOVE(newid, oldid, server);
+ MOVE(newid, oldid, creds);
+ MOVE(newid, oldid, tkt_life);
+ MOVE(newid, oldid, renew_life);
+ MOVE(newid, oldid, key);
+ MOVE(newid, oldid, kdc_offset);
+#undef MOVE
+ }
+
+ HEIMDAL_MUTEX_unlock(&oldid->mutex);
+ HEIMDAL_MUTEX_unlock(&newid->mutex);
+
+ kcm_release_ccache(context, oldid);
+ kcm_release_ccache(context, newid);
+
+ ret = kcm_ccache_destroy_client(context, client, oldname);
+ if (ret == 0)
+ kcm_drop_default_cache(context, client, oldname);
+
+ free(oldname);
+
+ return ret;
+}
+
+static krb5_error_code
+kcm_op_get_cache_uuid_list(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+{
+ KCM_LOG_REQUEST(context, client, opcode);
+
+ return kcm_ccache_get_uuids(context, client, opcode, response);
+}
+
+static krb5_error_code
+kcm_op_get_cache_by_uuid(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+{
+ krb5_error_code ret;
+ kcmuuid_t uuid;
+ ssize_t sret;
+ kcm_ccache cache;
+
+ KCM_LOG_REQUEST(context, client, opcode);
+
+ sret = krb5_storage_read(request, &uuid, sizeof(uuid));
+ if (sret != sizeof(uuid)) {
+ krb5_clear_error_message(context);
+ return KRB5_CC_IO;
+ }
+
+ ret = kcm_ccache_resolve_by_uuid(context, uuid, &cache);
+ if (ret)
+ return ret;
+
+ ret = kcm_access(context, client, opcode, cache);
+ if (ret)
+ ret = KRB5_FCC_NOFILE;
+
+ if (ret == 0)
+ ret = krb5_store_stringz(response, cache->name);
+
+ kcm_release_ccache(context, cache);
+
+ return ret;
+}
+
+struct kcm_default_cache *default_caches;
+
+static krb5_error_code
+kcm_op_get_default_cache(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+{
+ struct kcm_default_cache *c;
+ krb5_error_code ret;
+ const char *name = NULL;
+ char *n = NULL;
+ int aret;
+
+ KCM_LOG_REQUEST(context, client, opcode);
+
+ for (c = default_caches; c != NULL; c = c->next) {
+ if (kcm_is_same_session(client, c->uid, c->session)) {
+ name = c->name;
+ break;
+ }
+ }
+ if (name == NULL)
+ name = n = kcm_ccache_first_name(client);
+
+ if (name == NULL) {
+ aret = asprintf(&n, "%d", (int)client->uid);
+ if (aret != -1)
+ name = n;
+ }
+ if (name == NULL)
+ return ENOMEM;
+ ret = krb5_store_stringz(response, name);
+ if (n)
+ free(n);
+ return ret;
+}
+
+static void
+kcm_drop_default_cache(krb5_context context, kcm_client *client, char *name)
+{
+ struct kcm_default_cache **c;
+
+ for (c = &default_caches; *c != NULL; c = &(*c)->next) {
+ if (!kcm_is_same_session(client, (*c)->uid, (*c)->session))
+ continue;
+ if (strcmp((*c)->name, name) == 0) {
+ struct kcm_default_cache *h = *c;
+ *c = (*c)->next;
+ free(h->name);
+ free(h);
+ break;
+ }
+ }
+}
+
+static krb5_error_code
+kcm_op_set_default_cache(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+{
+ struct kcm_default_cache *c;
+ krb5_error_code ret;
+ char *name;
+
+ ret = krb5_ret_stringz(request, &name);
+ if (ret)
+ return ret;
+
+ KCM_LOG_REQUEST_NAME(context, client, opcode, name);
+
+ for (c = default_caches; c != NULL; c = c->next) {
+ if (kcm_is_same_session(client, c->uid, c->session))
+ break;
+ }
+ if (c == NULL) {
+ c = malloc(sizeof(*c));
+ if (c == NULL) {
+ free(name);
+ return ENOMEM;
+ }
+ c->session = client->session;
+ c->uid = client->uid;
+ c->name = name;
+
+ c->next = default_caches;
+ default_caches = c;
+ } else {
+ free(c->name);
+ c->name = name;
+ }
+
+ return 0;
+}
+
+static krb5_error_code
+kcm_op_get_kdc_offset(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+{
+ krb5_error_code ret;
+ kcm_ccache ccache;
+ char *name;
+
+ ret = krb5_ret_stringz(request, &name);
+ if (ret)
+ return ret;
+
+ KCM_LOG_REQUEST_NAME(context, client, opcode, name);
+
+ ret = kcm_ccache_resolve_client(context, client, opcode, name, &ccache);
+ free(name);
+ if (ret)
+ return ret;
+
+ HEIMDAL_MUTEX_lock(&ccache->mutex);
+ ret = krb5_store_int32(response, ccache->kdc_offset);
+ HEIMDAL_MUTEX_unlock(&ccache->mutex);
+
+ kcm_release_ccache(context, ccache);
+
+ return ret;
+}
+
+static krb5_error_code
+kcm_op_set_kdc_offset(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+{
+ krb5_error_code ret;
+ kcm_ccache ccache;
+ int32_t offset;
+ char *name;
+
+ ret = krb5_ret_stringz(request, &name);
+ if (ret)
+ return ret;
+
+ KCM_LOG_REQUEST_NAME(context, client, opcode, name);
+
+ ret = krb5_ret_int32(request, &offset);
+ if (ret) {
+ free(name);
+ return ret;
+ }
+
+ ret = kcm_ccache_resolve_client(context, client, opcode, name, &ccache);
+ free(name);
+ if (ret)
+ return ret;
+
+ HEIMDAL_MUTEX_lock(&ccache->mutex);
+ ccache->kdc_offset = offset;
+ HEIMDAL_MUTEX_unlock(&ccache->mutex);
+
+ kcm_release_ccache(context, ccache);
+
+ return ret;
+}
+
+struct kcm_ntlm_cred {
+ kcmuuid_t uuid;
+ char *user;
+ char *domain;
+ krb5_data nthash;
+ uid_t uid;
+ pid_t session;
+ struct kcm_ntlm_cred *next;
+};
+
+static struct kcm_ntlm_cred *ntlm_head;
+
+static void
+free_cred(struct kcm_ntlm_cred *cred)
+{
+ free(cred->user);
+ free(cred->domain);
+ krb5_data_free(&cred->nthash);
+ free(cred);
+}
+
+
+/*
+ * name
+ * domain
+ * ntlm hash
+ *
+ * Reply:
+ * uuid
+ */
+
+static struct kcm_ntlm_cred *
+find_ntlm_cred(const char *user, const char *domain, kcm_client *client)
+{
+ struct kcm_ntlm_cred *c;
+
+ for (c = ntlm_head; c != NULL; c = c->next)
+ if ((user[0] == '\0' || strcmp(user, c->user) == 0) &&
+ (domain == NULL || strcmp(domain, c->domain) == 0) &&
+ kcm_is_same_session(client, c->uid, c->session))
+ return c;
+
+ return NULL;
+}
+
+static krb5_error_code
+kcm_op_add_ntlm_cred(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+{
+ struct kcm_ntlm_cred *cred, *c;
+ krb5_error_code ret;
+
+ cred = calloc(1, sizeof(*cred));
+ if (cred == NULL)
+ return ENOMEM;
+
+ RAND_bytes(cred->uuid, sizeof(cred->uuid));
+
+ ret = krb5_ret_stringz(request, &cred->user);
+ if (ret)
+ goto error;
+
+ ret = krb5_ret_stringz(request, &cred->domain);
+ if (ret)
+ goto error;
+
+ ret = krb5_ret_data(request, &cred->nthash);
+ if (ret)
+ goto error;
+
+ /* search for dups */
+ c = find_ntlm_cred(cred->user, cred->domain, client);
+ if (c) {
+ krb5_data hash = c->nthash;
+ c->nthash = cred->nthash;
+ cred->nthash = hash;
+ free_cred(cred);
+ cred = c;
+ } else {
+ cred->next = ntlm_head;
+ ntlm_head = cred;
+ }
+
+ cred->uid = client->uid;
+ cred->session = client->session;
+
+ /* write response */
+ (void)krb5_storage_write(response, &cred->uuid, sizeof(cred->uuid));
+
+ return 0;
+
+ error:
+ free_cred(cred);
+
+ return ret;
+}
+
+/*
+ * { "HAVE_NTLM_CRED", NULL },
+ *
+ * input:
+ * name
+ * domain
+ */
+
+static krb5_error_code
+kcm_op_have_ntlm_cred(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+{
+ struct kcm_ntlm_cred *c;
+ char *user = NULL, *domain = NULL;
+ krb5_error_code ret;
+
+ ret = krb5_ret_stringz(request, &user);
+ if (ret)
+ goto error;
+
+ ret = krb5_ret_stringz(request, &domain);
+ if (ret)
+ goto error;
+
+ if (domain[0] == '\0') {
+ free(domain);
+ domain = NULL;
+ }
+
+ c = find_ntlm_cred(user, domain, client);
+ if (c == NULL)
+ ret = ENOENT;
+
+ error:
+ free(user);
+ if (domain)
+ free(domain);
+
+ return ret;
+}
+
+/*
+ * { "DEL_NTLM_CRED", NULL },
+ *
+ * input:
+ * name
+ * domain
+ */
+
+static krb5_error_code
+kcm_op_del_ntlm_cred(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+{
+ struct kcm_ntlm_cred **cp, *c;
+ char *user = NULL, *domain = NULL;
+ krb5_error_code ret;
+
+ ret = krb5_ret_stringz(request, &user);
+ if (ret)
+ goto error;
+
+ ret = krb5_ret_stringz(request, &domain);
+ if (ret)
+ goto error;
+
+ for (cp = &ntlm_head; *cp != NULL; cp = &(*cp)->next) {
+ if (strcmp(user, (*cp)->user) == 0 && strcmp(domain, (*cp)->domain) == 0 &&
+ kcm_is_same_session(client, (*cp)->uid, (*cp)->session))
+ {
+ c = *cp;
+ *cp = c->next;
+
+ free_cred(c);
+ break;
+ }
+ }
+
+ error:
+ free(user);
+ free(domain);
+
+ return ret;
+}
+
+/*
+ * { "DO_NTLM_AUTH", NULL },
+ *
+ * input:
+ * name:string
+ * domain:string
+ * type2:data
+ *
+ * reply:
+ * type3:data
+ * flags:int32
+ * session-key:data
+ */
+
+#define NTLM_FLAG_SESSIONKEY 1
+#define NTLM_FLAG_NTLM2_SESSION 2
+#define NTLM_FLAG_KEYEX 4
+
+static krb5_error_code
+kcm_op_do_ntlm(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+{
+ struct kcm_ntlm_cred *c;
+ struct ntlm_type2 type2;
+ struct ntlm_type3 type3;
+ char *user = NULL, *domain = NULL;
+ struct ntlm_buf ndata, sessionkey;
+ krb5_data data;
+ krb5_error_code ret;
+ uint32_t flags = 0;
+
+ memset(&type2, 0, sizeof(type2));
+ memset(&type3, 0, sizeof(type3));
+ sessionkey.data = NULL;
+ sessionkey.length = 0;
+
+ ret = krb5_ret_stringz(request, &user);
+ if (ret)
+ goto error;
+
+ ret = krb5_ret_stringz(request, &domain);
+ if (ret)
+ goto error;
+
+ if (domain[0] == '\0') {
+ free(domain);
+ domain = NULL;
+ }
+
+ c = find_ntlm_cred(user, domain, client);
+ if (c == NULL) {
+ ret = EINVAL;
+ goto error;
+ }
+
+ ret = krb5_ret_data(request, &data);
+ if (ret)
+ goto error;
+
+ ndata.data = data.data;
+ ndata.length = data.length;
+
+ ret = heim_ntlm_decode_type2(&ndata, &type2);
+ krb5_data_free(&data);
+ if (ret)
+ goto error;
+
+ if (domain && strcmp(domain, type2.targetname) == 0) {
+ ret = EINVAL;
+ goto error;
+ }
+
+ type3.username = c->user;
+ type3.flags = type2.flags;
+ type3.targetname = type2.targetname;
+ type3.ws = rk_UNCONST("workstation");
+
+ /*
+ * NTLM Version 1 if no targetinfo buffer.
+ */
+
+ if (1 || type2.targetinfo.length == 0) {
+ struct ntlm_buf tmpsesskey;
+
+ if (type2.flags & NTLM_NEG_NTLM2_SESSION) {
+ unsigned char nonce[8];
+
+ if (RAND_bytes(nonce, sizeof(nonce)) != 1) {
+ ret = EINVAL;
+ goto error;
+ }
+
+ ret = heim_ntlm_calculate_ntlm2_sess(nonce,
+ type2.challenge,
+ c->nthash.data,
+ &type3.lm,
+ &type3.ntlm);
+ } else {
+ ret = heim_ntlm_calculate_ntlm1(c->nthash.data,
+ c->nthash.length,
+ type2.challenge,
+ &type3.ntlm);
+
+ }
+ if (ret)
+ goto error;
+
+ ret = heim_ntlm_build_ntlm1_master(c->nthash.data,
+ c->nthash.length,
+ &tmpsesskey,
+ &type3.sessionkey);
+ if (ret) {
+ if (type3.lm.data)
+ free(type3.lm.data);
+ if (type3.ntlm.data)
+ free(type3.ntlm.data);
+ goto error;
+ }
+
+ free(tmpsesskey.data);
+ flags |= NTLM_FLAG_SESSIONKEY;
+#if 0
+ } else {
+ struct ntlm_buf sessionkey;
+ unsigned char ntlmv2[16];
+ struct ntlm_targetinfo ti;
+
+ /* verify infotarget */
+
+ ret = heim_ntlm_decode_targetinfo(&type2.targetinfo, 1, &ti);
+ if(ret) {
+ _gss_ntlm_delete_sec_context(minor_status,
+ context_handle, NULL);
+ *minor_status = ret;
+ return GSS_S_FAILURE;
+ }
+
+ if (ti.domainname && strcmp(ti.domainname, name->domain) != 0) {
+ _gss_ntlm_delete_sec_context(minor_status,
+ context_handle, NULL);
+ *minor_status = EINVAL;
+ return GSS_S_FAILURE;
+ }
+
+ ret = heim_ntlm_calculate_ntlm2(ctx->client->key.data,
+ ctx->client->key.length,
+ type3.username,
+ name->domain,
+ type2.challenge,
+ &type2.targetinfo,
+ ntlmv2,
+ &type3.ntlm);
+ if (ret) {
+ _gss_ntlm_delete_sec_context(minor_status,
+ context_handle, NULL);
+ *minor_status = ret;
+ return GSS_S_FAILURE;
+ }
+
+ ret = heim_ntlm_build_ntlm1_master(ntlmv2, sizeof(ntlmv2),
+ &sessionkey,
+ &type3.sessionkey);
+ memset(ntlmv2, 0, sizeof(ntlmv2));
+ if (ret) {
+ _gss_ntlm_delete_sec_context(minor_status,
+ context_handle, NULL);
+ *minor_status = ret;
+ return GSS_S_FAILURE;
+ }
+
+ flags |= NTLM_FLAG_NTLM2_SESSION |
+ NTLM_FLAG_SESSION;
+
+ if (type3.flags & NTLM_NEG_KEYEX)
+ flags |= NTLM_FLAG_KEYEX;
+
+ ret = krb5_data_copy(&ctx->sessionkey,
+ sessionkey.data, sessionkey.length);
+ free(sessionkey.data);
+ if (ret) {
+ _gss_ntlm_delete_sec_context(minor_status,
+ context_handle, NULL);
+ *minor_status = ret;
+ return GSS_S_FAILURE;
+ }
+#endif
+ }
+
+#if 0
+ if (flags & NTLM_FLAG_NTLM2_SESSION) {
+ _gss_ntlm_set_key(&ctx->u.v2.send, 0, (ctx->flags & NTLM_NEG_KEYEX),
+ ctx->sessionkey.data,
+ ctx->sessionkey.length);
+ _gss_ntlm_set_key(&ctx->u.v2.recv, 1, (ctx->flags & NTLM_NEG_KEYEX),
+ ctx->sessionkey.data,
+ ctx->sessionkey.length);
+ } else {
+ flags |= NTLM_FLAG_SESSION;
+ RC4_set_key(&ctx->u.v1.crypto_recv.key,
+ ctx->sessionkey.length,
+ ctx->sessionkey.data);
+ RC4_set_key(&ctx->u.v1.crypto_send.key,
+ ctx->sessionkey.length,
+ ctx->sessionkey.data);
+ }
+#endif
+
+ ret = heim_ntlm_encode_type3(&type3, &ndata, NULL);
+ if (ret)
+ goto error;
+
+ data.data = ndata.data;
+ data.length = ndata.length;
+ ret = krb5_store_data(response, data);
+ heim_ntlm_free_buf(&ndata);
+ if (ret) goto error;
+
+ ret = krb5_store_int32(response, flags);
+ if (ret) goto error;
+
+ data.data = sessionkey.data;
+ data.length = sessionkey.length;
+
+ ret = krb5_store_data(response, data);
+ if (ret) goto error;
+
+ error:
+ free(type3.username);
+ heim_ntlm_free_type2(&type2);
+ free(user);
+ if (domain)
+ free(domain);
+
+ return ret;
+}
+
+
+/*
+ * { "GET_NTLM_UUID_LIST", NULL }
+ *
+ * reply:
+ * 1 user domain
+ * 0 [ end of list ]
+ */
+
+static krb5_error_code
+kcm_op_get_ntlm_user_list(krb5_context context,
+ kcm_client *client,
+ kcm_operation opcode,
+ krb5_storage *request,
+ krb5_storage *response)
+{
+ struct kcm_ntlm_cred *c;
+ krb5_error_code ret;
+
+ for (c = ntlm_head; c != NULL; c = c->next) {
+ if (!kcm_is_same_session(client, c->uid, c->session))
+ continue;
+
+ ret = krb5_store_uint32(response, 1);
+ if (ret)
+ return ret;
+ ret = krb5_store_stringz(response, c->user);
+ if (ret)
+ return ret;
+ ret = krb5_store_stringz(response, c->domain);
+ if (ret)
+ return ret;
+ }
+ return krb5_store_uint32(response, 0);
+}
+
+/*
+ *
+ */
+
+static struct kcm_op kcm_ops[] = {
+ { "NOOP", kcm_op_noop },
+ { "GET_NAME", kcm_op_get_name },
+ { "RESOLVE", kcm_op_noop },
+ { "GEN_NEW", kcm_op_gen_new },
+ { "INITIALIZE", kcm_op_initialize },
+ { "DESTROY", kcm_op_destroy },
+ { "STORE", kcm_op_store },
+ { "RETRIEVE", kcm_op_retrieve },
+ { "GET_PRINCIPAL", kcm_op_get_principal },
+ { "GET_CRED_UUID_LIST", kcm_op_get_cred_uuid_list },
+ { "GET_CRED_BY_UUID", kcm_op_get_cred_by_uuid },
+ { "REMOVE_CRED", kcm_op_remove_cred },
+ { "SET_FLAGS", kcm_op_set_flags },
+ { "CHOWN", kcm_op_chown },
+ { "CHMOD", kcm_op_chmod },
+ { "GET_INITIAL_TICKET", kcm_op_get_initial_ticket },
+ { "GET_TICKET", kcm_op_get_ticket },
+ { "MOVE_CACHE", kcm_op_move_cache },
+ { "GET_CACHE_UUID_LIST", kcm_op_get_cache_uuid_list },
+ { "GET_CACHE_BY_UUID", kcm_op_get_cache_by_uuid },
+ { "GET_DEFAULT_CACHE", kcm_op_get_default_cache },
+ { "SET_DEFAULT_CACHE", kcm_op_set_default_cache },
+ { "GET_KDC_OFFSET", kcm_op_get_kdc_offset },
+ { "SET_KDC_OFFSET", kcm_op_set_kdc_offset },
+ { "ADD_NTLM_CRED", kcm_op_add_ntlm_cred },
+ { "HAVE_USER_CRED", kcm_op_have_ntlm_cred },
+ { "DEL_NTLM_CRED", kcm_op_del_ntlm_cred },
+ { "DO_NTLM_AUTH", kcm_op_do_ntlm },
+ { "GET_NTLM_USER_LIST", kcm_op_get_ntlm_user_list }
+};
+
+
+const char *
+kcm_op2string(kcm_operation opcode)
+{
+ if (opcode >= sizeof(kcm_ops)/sizeof(kcm_ops[0]))
+ return "Unknown operation";
+
+ return kcm_ops[opcode].name;
+}
+
+krb5_error_code
+kcm_dispatch(krb5_context context,
+ kcm_client *client,
+ krb5_data *req_data,
+ krb5_data *resp_data)
+{
+ krb5_error_code ret;
+ kcm_method method;
+ krb5_storage *req_sp = NULL;
+ krb5_storage *resp_sp = NULL;
+ uint16_t opcode;
+
+ krb5_data_zero(resp_data);
+ resp_sp = krb5_storage_emem();
+ if (resp_sp == NULL) {
+ return ENOMEM;
+ }
+
+ if (client->pid == -1) {
+ kcm_log(0, "Client had invalid process number");
+ ret = KRB5_FCC_INTERNAL;
+ goto out;
+ }
+
+ req_sp = krb5_storage_from_data(req_data);
+ if (req_sp == NULL) {
+ kcm_log(0, "Process %d: failed to initialize storage from data",
+ client->pid);
+ ret = KRB5_CC_IO;
+ goto out;
+ }
+
+ ret = krb5_ret_uint16(req_sp, &opcode);
+ if (ret) {
+ kcm_log(0, "Process %d: didn't send a message", client->pid);
+ goto out;
+ }
+
+ if (opcode >= sizeof(kcm_ops)/sizeof(kcm_ops[0])) {
+ kcm_log(0, "Process %d: invalid operation code %d",
+ client->pid, opcode);
+ ret = KRB5_FCC_INTERNAL;
+ goto out;
+ }
+ method = kcm_ops[opcode].method;
+ if (method == NULL) {
+ kcm_log(0, "Process %d: operation code %s not implemented",
+ client->pid, kcm_op2string(opcode));
+ ret = KRB5_FCC_INTERNAL;
+ goto out;
+ }
+
+ /* seek past place for status code */
+ krb5_storage_seek(resp_sp, 4, SEEK_SET);
+
+ ret = (*method)(context, client, opcode, req_sp, resp_sp);
+
+out:
+ if (req_sp != NULL) {
+ krb5_storage_free(req_sp);
+ }
+
+ if (resp_sp) {
+ krb5_error_code ret2;
+
+ krb5_storage_seek(resp_sp, 0, SEEK_SET);
+ ret2 = krb5_store_int32(resp_sp, ret);
+ if (ret2 == 0)
+ ret2 = krb5_storage_to_data(resp_sp, resp_data);
+ krb5_storage_free(resp_sp);
+ if (ret2)
+ ret = ret2;
+ }
+
+ return ret;
+}
+