freebsd-src/crypto/heimdal/kcm/protocol.c
Cy Schubert ed549cb0c5 heimdal: Fix multiple security vulnerabilities
The following issues are patched:

 - CVE-2022-42898 PAC parse integer overflows
 - CVE-2022-3437 Overflows and non-constant time leaks in DES{,3} and arcfour
 - CVE-2021-44758 NULL dereference DoS in SPNEGO acceptors
 - CVE-2022-44640 Heimdal KDC: invalid free in ASN.1 codec

    Note that CVE-2022-44640 is a severe vulnerability, possibly a 10.0
    on the Common Vulnerability Scoring System (CVSS) v3, as we believe
    it should be possible to get an RCE on a KDC, which means that
    credentials can be compromised that can be used to impersonate
    anyone in a realm or forest of realms.

    Heimdal's ASN.1 compiler generates code that allows specially
    crafted DER encodings of CHOICEs to invoke the wrong free function
    on the decoded structure upon decode error.  This is known to impact
    the Heimdal KDC, leading to an invalid free() of an address partly
    or wholly under the control of the attacker, in turn leading to a
    potential remote code execution (RCE) vulnerability.

    This error affects the DER codec for all extensible CHOICE types
    used in Heimdal, though not all cases will be exploitable.  We have
    not completed a thorough analysis of all the Heimdal components
    affected, thus the Kerberos client, the X.509 library, and other
    parts, may be affected as well.

    This bug has been in Heimdal's ASN.1 compiler since 2005, but it may
    only affect Heimdal 1.6 and up.  It was first reported by Douglas
    Bagnall, though it had been found independently by the Heimdal
    maintainers via fuzzing a few weeks earlier.

    While no zero-day exploit is known, such an exploit will likely be
    available soon after public disclosure.

 - CVE-2019-14870: Validate client attributes in protocol-transition
 - CVE-2019-14870: Apply forwardable policy in protocol-transition
 - CVE-2019-14870: Always lookup impersonate client in DB

Sponsored by:	so (philip)
Obtained from:	so (philip)
Tested by:	philip, cy
MFC after:	immediately
2022-11-15 13:12:37 -08:00

1811 lines
37 KiB
C

/*
* 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, &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);
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;
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) {
asprintf(&n, "%d", (int)client->uid);
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)
return ENOMEM;
c->session = client->session;
c->uid = client->uid;
c->name = strdup(name);
c->next = default_caches;
default_caches = c;
} else {
free(c->name);
c->name = strdup(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 sessionkey;
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,
&sessionkey,
&type3.sessionkey);
if (ret) {
if (type3.lm.data)
free(type3.lm.data);
if (type3.ntlm.data)
free(type3.ntlm.data);
goto error;
}
free(sessionkey.data);
if (ret) {
if (type3.lm.data)
free(type3.lm.data);
if (type3.ntlm.data)
free(type3.ntlm.data);
goto error;
}
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);
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;
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);
}
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;
}