diff options
Diffstat (limited to 'crypto/heimdal/kcm/protocol.c')
-rw-r--r-- | crypto/heimdal/kcm/protocol.c | 1046 |
1 files changed, 1046 insertions, 0 deletions
diff --git a/crypto/heimdal/kcm/protocol.c b/crypto/heimdal/kcm/protocol.c new file mode 100644 index 0000000..bb3c653 --- /dev/null +++ b/crypto/heimdal/kcm/protocol.c @@ -0,0 +1,1046 @@ +/* + * Copyright (c) 2005, PADL Software Pty Ltd. + * 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" + +RCSID("$Id: protocol.c 22112 2007-12-03 19:34:33Z lha $"); + +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); + + 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_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 0; +} + +/* + * Request: + * NameZ + * + * Response: + * Cursor + * + */ +static krb5_error_code +kcm_op_get_first(krb5_context context, + kcm_client *client, + kcm_operation opcode, + krb5_storage *request, + krb5_storage *response) +{ + krb5_error_code ret; + kcm_ccache ccache; + uint32_t cursor; + 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; + } + + ret = kcm_cursor_new(context, client->pid, ccache, &cursor); + if (ret) { + kcm_release_ccache(context, &ccache); + free(name); + return ret; + } + + ret = krb5_store_int32(response, cursor); + + free(name); + kcm_release_ccache(context, &ccache); + + return ret; +} + +/* + * Request: + * NameZ + * Cursor + * + * Response: + * Creds + */ +static krb5_error_code +kcm_op_get_next(krb5_context context, + kcm_client *client, + kcm_operation opcode, + krb5_storage *request, + krb5_storage *response) +{ + krb5_error_code ret; + kcm_ccache ccache; + char *name; + uint32_t cursor; + kcm_cursor *c; + + ret = krb5_ret_stringz(request, &name); + if (ret) + return ret; + + KCM_LOG_REQUEST_NAME(context, client, opcode, name); + + ret = krb5_ret_uint32(request, &cursor); + if (ret) { + free(name); + return ret; + } + + ret = kcm_ccache_resolve_client(context, client, opcode, + name, &ccache); + if (ret) { + free(name); + return ret; + } + + ret = kcm_cursor_find(context, client->pid, ccache, cursor, &c); + if (ret) { + kcm_release_ccache(context, &ccache); + free(name); + return ret; + } + + HEIMDAL_MUTEX_lock(&ccache->mutex); + if (c->credp == NULL) { + ret = KRB5_CC_END; + } else { + ret = krb5_store_creds(response, &c->credp->cred); + c->credp = c->credp->next; + } + HEIMDAL_MUTEX_unlock(&ccache->mutex); + + free(name); + kcm_release_ccache(context, &ccache); + + return ret; +} + +/* + * Request: + * NameZ + * Cursor + * + * Response: + * + */ +static krb5_error_code +kcm_op_end_get(krb5_context context, + kcm_client *client, + kcm_operation opcode, + krb5_storage *request, + krb5_storage *response) +{ + krb5_error_code ret; + kcm_ccache ccache; + uint32_t cursor; + 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, &cursor); + if (ret) { + free(name); + return ret; + } + + ret = kcm_ccache_resolve_client(context, client, opcode, + name, &ccache); + if (ret) { + free(name); + return ret; + } + + ret = kcm_cursor_delete(context, client->pid, ccache, cursor); + + free(name); + 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(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); + + if (ret == 0) + krb5_free_cred_contents(context, out); + + free(name); + + return ret; +} + +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_FIRST", kcm_op_get_first }, + { "GET_NEXT", kcm_op_get_next }, + { "END_GET", kcm_op_end_get }, + { "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 } +}; + + +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; + + 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; + + /* 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); + } + + krb5_storage_seek(resp_sp, 0, SEEK_SET); + krb5_store_int32(resp_sp, ret); + + ret = krb5_storage_to_data(resp_sp, resp_data); + krb5_storage_free(resp_sp); + + return ret; +} + |