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
1811 lines
37 KiB
C
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, ¬_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;
|
|
}
|
|
|