freebsd-dev/crypto/heimdal/kcm/protocol.c
2008-05-07 13:39:42 +00:00

1047 lines
20 KiB
C

/*
* 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, &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(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;
}