diff options
Diffstat (limited to 'third_party/heimdal/kcm/protocol.c')
-rw-r--r-- | third_party/heimdal/kcm/protocol.c | 1814 |
1 files changed, 1814 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..31f1762 --- /dev/null +++ b/third_party/heimdal/kcm/protocol.c @@ -0,0 +1,1814 @@ +/* + * 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 && + mcreds.server->name.name_string.len == 2 && + strcmp(mcreds.server->name.name_string.val[0], KRB5_TGS_NAME) == 0) + { + 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, ¬_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; +} + |