/* * 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 #include #ifdef __APPLE_PRIVATE__ #include #endif 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) { /* * Only same session * Let user access any credential regardless of session. * Deny otherwise. */ if (use_uid_matching && client->uid != 0 && client->uid == uid) { kcm_log(1, "allowed (uid matching)"); return 1; } else if (client->session == session) { kcm_log(1, "allowed (session matching)"); return 1; } kcm_log(1, "denied"); return 0; } 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); 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); kcm_release_ccache(context, ccache); kcm_data_changed = 1; 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); kcm_data_changed = 1; 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; } if (creds.client && krb5_principal_is_root_krbtgt(context, creds.server)) kcm_ccache_enqueue_default(context, ccache, &creds); free(name); kcm_release_ccache(context, ccache); kcm_data_changed = 1; 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; 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); goto out; } if (flags & KRB5_TC_MATCH_REFERRAL) flags |= KRB5_TC_DONT_MATCH_REALM; ret = krb5_ret_creds_tag(request, &mcreds); if (ret) { free(name); goto out; } if (disallow_getting_krbtgt && mcreds.server->name.name_string.len == 2 && strcmp(mcreds.server->name.name_string.val[0], KRB5_TGS_NAME) == 0) { krb5_free_cred_contents(context, &mcreds); ret = KRB5_FCC_PERM; free(name); goto out; } ret = kcm_ccache_resolve_client(context, client, opcode, name, &ccache); if (ret) { krb5_free_cred_contents(context, &mcreds); goto out; } ret = kcm_ccache_retrieve_cred(context, ccache, flags, &mcreds, &credp); if (ret == 0) ret = krb5_store_creds(response, credp); kcm_release_ccache(context, ccache); krb5_free_cred_contents(context, &mcreds); out: free(name); 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); kcm_data_changed = 1; 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; } free(name); kcm_release_ccache(context, ccache); kcm_data_changed = 1; 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); kcm_data_changed = 1; return ret; } /* * Protocol extensions for moving ticket acquisition responsibility * from client to KCM follow. */ /* * Request: * NameZ * clientPrincipal * ServerPrincipalPresent * ServerPrincipal OPTIONAL * password * * 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) { char *name, *password; krb5_error_code ret; kcm_ccache ccache; int8_t not_tgt = 0; krb5_principal cprincipal = NULL; krb5_principal server = NULL; ret = krb5_ret_stringz(request, &name); if (ret) return ret; KCM_LOG_REQUEST_NAME(context, client, opcode, name); ret = krb5_ret_principal(request, &cprincipal); if (ret) { free(name); return ret; } ret = krb5_ret_int8(request, ¬_tgt); if (ret) { free(name); return ret; } if (not_tgt) { ret = krb5_ret_principal(request, &server); } else { ret = krb5_make_principal(context,&server, cprincipal->realm, KRB5_TGS_NAME, cprincipal->realm, NULL); } if (ret) { krb5_free_principal(context, cprincipal); free(name); return ret; } ret = krb5_ret_stringz(request, &password); if (ret) { free(name); krb5_free_principal(context, cprincipal); 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->client) krb5_free_principal(context, ccache->client); if (ccache->server) krb5_free_principal(context, ccache->server); if (ccache->password) { memset(ccache->password, 0, strlen(ccache->password)); free(ccache->password); } ccache->client = cprincipal; ccache->server = server; ccache->password = password; ccache->flags |= KCM_FLAGS_USE_PASSWORD; kcm_ccache_update_acquire_status(kcm_context, ccache, KCM_STATUS_ACQUIRE_START, 0); HEIMDAL_MUTEX_unlock(&ccache->mutex); kcm_release_ccache(context, ccache); kcm_data_changed = 1; } else { krb5_free_principal(context, cprincipal); if (server) krb5_free_principal(context, server); memset(password, 0, strlen(password)); free(password); } free(name); 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); kcm_data_changed = 1; 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; } /* if we are renaming to ourself, done! */ if (strcmp(newname, oldname) == 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 */ { struct 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, password); MOVE(newid, oldid, keytab); MOVE(newid, oldid, kdc_offset); MOVE(newid, oldid, expire); #undef MOVE } kcm_update_renew_time(newid); if (newid->expire && (newid->flags & KCM_MASK_KEY_PRESENT) == 0 && time(NULL) < newid->expire) kcm_update_expire_time(newid, newid->expire); 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); kcm_data_changed = 1; 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 = calloc(1, sizeof(*c)); if (c == NULL) { free(name); return ENOMEM; } c->session = client->session; c->uid = client->uid; c->name = name; c->next = default_caches; default_caches = c; } else { free(c->name); c->name = name; } return 0; } static krb5_error_code kcm_op_get_kdc_offset(krb5_context context, kcm_client *client, kcm_operation opcode, krb5_storage *request, krb5_storage *response) { krb5_error_code ret; kcm_ccache ccache; char *name; ret = krb5_ret_stringz(request, &name); if (ret) return ret; KCM_LOG_REQUEST_NAME(context, client, opcode, name); ret = kcm_ccache_resolve_client(context, client, opcode, name, &ccache); free(name); if (ret) return ret; HEIMDAL_MUTEX_lock(&ccache->mutex); ret = krb5_store_int32(response, ccache->kdc_offset); HEIMDAL_MUTEX_unlock(&ccache->mutex); kcm_release_ccache(context, ccache); return ret; } static krb5_error_code kcm_op_set_kdc_offset(krb5_context context, kcm_client *client, kcm_operation opcode, krb5_storage *request, krb5_storage *response) { krb5_error_code ret; kcm_ccache ccache; int32_t offset; char *name; ret = krb5_ret_stringz(request, &name); if (ret) return ret; KCM_LOG_REQUEST_NAME(context, client, opcode, name); ret = krb5_ret_int32(request, &offset); if (ret) { free(name); return ret; } ret = kcm_ccache_resolve_client(context, client, opcode, name, &ccache); free(name); if (ret) return ret; HEIMDAL_MUTEX_lock(&ccache->mutex); ccache->kdc_offset = offset; HEIMDAL_MUTEX_unlock(&ccache->mutex); kcm_release_ccache(context, ccache); return ret; } static krb5_error_code kcm_op_retain_kcred(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; } HEIMDAL_MUTEX_lock(&ccache->mutex); ccache->holdcount++; kcm_log(1, "retain_kcred: holdcount for %s is %ld", name, ccache->holdcount); HEIMDAL_MUTEX_unlock(&ccache->mutex); kcm_release_ccache(context, ccache); free(name); kcm_data_changed = 1; return 0; } static krb5_error_code kcm_op_release_kcred(krb5_context context, kcm_client *client, kcm_operation opcode, krb5_storage *request, krb5_storage *response) { krb5_error_code ret; kcm_ccache ccache; char *name; int destroy = 0; 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; } HEIMDAL_MUTEX_lock(&ccache->mutex); ccache->holdcount--; if (ccache->holdcount < 1) destroy = 1; kcm_log(1, "release_kcred: holdcount for %s is %ld", name, ccache->holdcount); HEIMDAL_MUTEX_unlock(&ccache->mutex); kcm_release_ccache(context, ccache); if (destroy) { kcm_log(1, "holdcount for %s is zero, removing", name); ret = kcm_ccache_destroy_client(context, client, name); if (ret == 0) kcm_drop_default_cache(context, client, name); } free(name); kcm_data_changed = 1; return 0; } static krb5_error_code kcm_op_get_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; krb5_uuid uuid; 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); memcpy(uuid, ccache->uuid, sizeof(uuid)); HEIMDAL_MUTEX_unlock(&ccache->mutex); kcm_release_ccache(context, ccache); (void)krb5_storage_write(response, uuid, sizeof(uuid)); return 0; } /* * */ enum kcm_cred_type { KCM_NTLM_CRED, KCM_SCRAM_CRED }; struct kcm_ntlm_cred { enum kcm_cred_type type; kcmuuid_t uuid; char *user; char *domain; #define nthash u.ntlm union { krb5_data ntlm; char *password; } u; uid_t uid; pid_t session; long refcount; heim_dict_t labels; struct kcm_ntlm_cred *next; }; static struct kcm_ntlm_cred *ntlm_head; static HEIMDAL_MUTEX cred_mutex = HEIMDAL_MUTEX_INITIALIZER; #define CHECK(s) do { if ((s)) { goto out; } } while(0) static krb5_error_code kcm_unparse_digest_one(krb5_storage *inner, struct kcm_ntlm_cred *c) { __block krb5_error_code ret; if (c->type == KCM_NTLM_CRED) CHECK(ret = krb5_store_stringz(inner, "ntlm-cache")); else if (c->type == KCM_SCRAM_CRED) CHECK(ret = krb5_store_stringz(inner, "scram-cache")); else heim_assert(false, "unknown cred type"); CHECK(ret = krb5_store_uuid(inner, c->uuid)); CHECK(ret = krb5_store_stringz(inner, c->user)); if (c->domain) { CHECK(ret = krb5_store_uint8(inner, 1)); CHECK(ret = krb5_store_stringz(inner, c->domain)); } else { CHECK(ret = krb5_store_uint8(inner, 0)); } if (c->type == KCM_NTLM_CRED) CHECK(ret = krb5_store_data(inner, c->u.ntlm)); else if (c->type == KCM_SCRAM_CRED) CHECK(ret = krb5_store_stringz(inner, c->u.password)); CHECK(ret = krb5_store_int32(inner, c->uid)); CHECK(ret = krb5_store_int32(inner, c->session)); CHECK(ret = krb5_store_uint32(inner, c->refcount)); heim_dict_iterate(c->labels, ^(heim_object_t key, heim_object_t value) { const char *k = heim_string_get_utf8(key); heim_data_t d = value; krb5_data data; data.data = d->data; data.length = d->length; if (ret) return; ret = krb5_store_uint8(inner, 1); if (ret) return; ret = krb5_store_stringz(inner, k); if (ret) return; ret = krb5_store_data(inner, data); if (ret) return; }); CHECK(ret); CHECK(ret = krb5_store_uint8(inner, 0)); out: return ret; } krb5_error_code kcm_unparse_digest_all(krb5_context context, krb5_storage *sp) { struct kcm_ntlm_cred *c; krb5_error_code r = 0; HEIMDAL_MUTEX_lock(&cred_mutex); for (c = ntlm_head; r == 0 && c != NULL; c = c->next) { r = kcm_unparse_wrap(sp, "digest-cache", c->session, ^(krb5_storage *inner) { return kcm_unparse_digest_one(inner, c); }); } if (r) kcm_log(10, "failed to write digest-cred: %d", r); HEIMDAL_MUTEX_unlock(&cred_mutex); return r; } krb5_error_code kcm_parse_digest_one(krb5_context context, krb5_storage *sp) { struct kcm_ntlm_cred *c; krb5_error_code ret; char *type = NULL; uint32_t u32; int32_t s32; uint8_t u8; c = calloc(1, sizeof(*c)); CHECK(ret = krb5_ret_stringz(sp, &type)); if (strcmp(type, "ntlm-cache") == 0) { c->type = KCM_NTLM_CRED; } else if (strcmp(type, "scram-cache") == 0) { c->type = KCM_SCRAM_CRED; } else { free(type); return EINVAL; } CHECK(ret = krb5_ret_uuid(sp, c->uuid)); CHECK(ret = krb5_ret_stringz(sp, &c->user)); CHECK(ret = krb5_ret_uint8(sp, &u8)); if (u8) { CHECK(ret = krb5_ret_stringz(sp, &c->domain)); } if (c->type == KCM_NTLM_CRED) CHECK(ret = krb5_ret_data(sp, &c->u.ntlm)); else if (c->type == KCM_SCRAM_CRED) CHECK(ret = krb5_ret_stringz(sp, &c->u.password)); CHECK(ret = krb5_ret_int32(sp, &s32)); c->uid = s32; CHECK(ret = krb5_ret_uint32(sp, &u32)); c->session = u32; CHECK(ret = krb5_ret_uint32(sp, &u32)); c->refcount = u32; c->labels = heim_dict_create(0); while (1) { krb5_data data; char *str; CHECK(ret = krb5_ret_uint8(sp, &u8)); if (u8 == 0) break; CHECK(ret = krb5_ret_stringz(sp, &str)); heim_string_t s = heim_string_create(str); free(str); CHECK(ret = krb5_ret_data(sp, &data)); heim_data_t d = heim_data_create(data.data, data.length); krb5_data_free(&data); heim_dict_add_value(c->labels, s, d); heim_release(s); heim_release(d); } c->next = ntlm_head; ntlm_head = c; out: free(type); if (ret) { kcm_log(10, "failed to read %s: %d", type, ret); /* free_cred(c); */ } return ret; } static void free_cred(struct kcm_ntlm_cred *cred) { free(cred->user); free(cred->domain); if (cred->type == KCM_NTLM_CRED) { krb5_data_free(&cred->nthash); } else if (cred->type == KCM_SCRAM_CRED) { free(cred->u.password); } else { abort(); } heim_release(cred->labels); free(cred); } static struct kcm_ntlm_cred * find_ntlm_cred(enum kcm_cred_type type, const char *user, const char *domain, kcm_client *client) { struct kcm_ntlm_cred *c; for (c = ntlm_head; c != NULL; c = c->next) if (c->type == type && (user[0] == '\0' || strcasecmp(user, c->user) == 0) && (domain == NULL || strcasecmp(domain, c->domain) == 0) && kcm_is_same_session(client, c->uid, c->session)) return c; return NULL; } static struct kcm_ntlm_cred * create_cred(enum kcm_cred_type type) { struct kcm_ntlm_cred *cred; cred = calloc(1, sizeof(*cred)); if (cred == NULL) return NULL; cred->type = type; cred->labels = heim_dict_create(0); cred->refcount = 1; CCRandomCopyBytes(kCCRandomDefault, cred->uuid, sizeof(cred->uuid)); return cred; } /* * name * domain * ntlm hash * * Reply: * uuid */ 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 = create_cred(KCM_NTLM_CRED); if (cred == NULL) return ENOMEM; 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; HEIMDAL_MUTEX_lock(&cred_mutex); /* search for dups */ c = find_ntlm_cred(KCM_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; HEIMDAL_MUTEX_unlock(&cred_mutex); /* write response */ (void)krb5_storage_write(response, &cred->uuid, sizeof(cred->uuid)); kcm_data_changed = 1; 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; HEIMDAL_MUTEX_lock(&cred_mutex); c = find_ntlm_cred(KCM_NTLM_CRED, user, domain, client); if (c == NULL) ret = ENOENT; kcm_log(10, "ntlm checking for ntlm cred for %s@%s, -> %s", user, domain, (c == NULL ? "no" : "yes")); if (c) (void)krb5_storage_write(response, &c->uuid, sizeof(c->uuid)); HEIMDAL_MUTEX_unlock(&cred_mutex); error: free(user); if (domain) free(domain); return ret; } /* * { "DEL_CRED", NULL }, * * input: * uuid */ static krb5_error_code kcm_op_del_cred(krb5_context context, kcm_client *client, kcm_operation opcode, krb5_storage *request, krb5_storage *response) { struct kcm_ntlm_cred **cp, *c; kcmuuid_t uuid; ssize_t sret; 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; } HEIMDAL_MUTEX_lock(&cred_mutex); for (cp = &ntlm_head; *cp != NULL; cp = &(*cp)->next) { if ((*cp)->type == KCM_NTLM_CRED && memcmp((*cp)->uuid, uuid, sizeof(uuid)) == 0 && kcm_is_same_session(client, (*cp)->uid, (*cp)->session)) { c = *cp; *cp = c->next; free_cred(c); kcm_data_changed = 1; break; } } HEIMDAL_MUTEX_unlock(&cred_mutex); return 0; } static struct ntlm_challenge { struct ntlm_challenge *next; uint8_t challenge[8]; time_t ts; } *ntlm_challenges = NULL; static void ntlm_delete_chain(struct ntlm_challenge *c) { while (c) { struct ntlm_challenge *next = c->next; free(c); c = next; } } static int ntlm_expiredp(struct ntlm_challenge *c, time_t now) { return c->ts > now - heim_ntlm_time_skew; } static int check_ntlm_challage(uint8_t chal[8]) { struct ntlm_challenge **q = &ntlm_challenges; time_t t = time(NULL); while (*q) { if (ntlm_expiredp(*q, t)) { struct ntlm_challenge *c = *q; *q = NULL; ntlm_delete_chain(c); return 0; } if (memcmp((*q)->challenge, chal, sizeof((*q)->challenge)) == 0) return EAUTH; q = &(*q)->next; } return 0; } /* * { "SET_NTLM_CHALLAGE", NULL } * * request: * challage 8 byte */ static krb5_error_code kcm_op_add_ntlm_challenge(krb5_context context, kcm_client *client, kcm_operation opcode, krb5_storage *request, krb5_storage *response) { uint8_t chal[8]; struct ntlm_challenge *c; ssize_t sret; KCM_LOG_REQUEST(context, client, opcode); if (client->uid != 0) return EAUTH; c = malloc(sizeof(*c)); if (c == NULL) return ENOMEM; sret = krb5_storage_read(request, c->challenge, sizeof(c->challenge)); if (sret != sizeof(chal)) { free(c); return KRB5_CC_IO; } c->ts = time(NULL); c->next = ntlm_challenges; ntlm_challenges = c; kcm_data_changed = 1; return 0; } krb5_error_code kcm_parse_ntlm_challenge_one(krb5_context context, krb5_storage *sp) { struct ntlm_challenge *c, **q; krb5_error_code ret; int32_t ts; ssize_t sret; c = malloc(sizeof(*c)); if (c == NULL) return ENOMEM; sret = krb5_storage_read(sp, c->challenge, sizeof(c->challenge)); if (sret != sizeof(c->challenge)) { free(c); return KRB5_CC_IO; } ret = krb5_ret_int32(sp, &ts); if (ret) { free(c); return ret; } c->ts = ts; c->next = NULL; if (ntlm_expiredp(c, time(NULL))) { free(c); } else { /* find end and add c, XXX performance */ for (q = &ntlm_challenges; *q != NULL; q = &(*q)->next) ; *q = c; } return 0; } krb5_error_code kcm_unparse_challenge_all(krb5_context context, krb5_storage *sp) { struct ntlm_challenge *c; krb5_error_code r = 0; time_t now = time(NULL); for (c = ntlm_challenges; r == 0 && c != NULL; c = c->next) { if (ntlm_expiredp(c, now)) /* stop when they have expired */ break; r = kcm_unparse_wrap(sp, "ntlm-chal", 0, ^(krb5_storage *inner) { krb5_error_code ret; CHECK(ret = krb5_storage_write(inner, c->challenge, sizeof(c->challenge))); CHECK(ret = krb5_store_int32(inner, c->ts)); out: return ret; }); } if (r) kcm_log(10, "failed to write ntlm-chal: %d", r); return r; } /* * */ static int ntlm_domain_is_hostname(const char *name) { return (name[0] == '\\'); } /* * { "DO_NTLM_AUTH", NULL }, * * input: * name:string * domain:string * type2:data * * reply: * type3:data * flags:int32 * session-key:data */ static krb5_error_code kcm_op_do_ntlm(krb5_context context, kcm_client *client, kcm_operation opcode, krb5_storage *request, krb5_storage *response) { #ifdef ENABLE_NTLM struct kcm_ntlm_cred *c; struct ntlm_type2 type2; struct ntlm_type3 type3; char *user = NULL, *domain = NULL, *targetname = NULL; struct ntlm_buf ndata, sessionkey, tidata; krb5_data type2data, cb, type1data, tempdata; krb5_error_code ret; uint32_t type1flags, flags = 0; const char *type = "unknown"; char flagname[256]; size_t mic_offset = 0; KCM_LOG_REQUEST(context, client, opcode); krb5_data_zero(&cb); krb5_data_zero(&type1data); krb5_data_zero(&type2data); memset(&tidata, 0, sizeof(tidata)); memset(&type2, 0, sizeof(type2)); memset(&type3, 0, sizeof(type3)); sessionkey.data = NULL; sessionkey.length = 0; HEIMDAL_MUTEX_lock(&cred_mutex); ret = krb5_ret_stringz(request, &user); if (ret) goto error; ret = krb5_ret_stringz(request, &domain); if (ret) goto error; kcm_log(10, "NTLM AUTH with cred %s\\%s", domain, user); c = find_ntlm_cred(KCM_NTLM_CRED, user, domain, client); if (c == NULL) { ret = EINVAL; goto error; } ret = krb5_ret_data(request, &type2data); if (ret) goto error; ret = krb5_ret_data(request, &cb); if (ret) goto error; ret = krb5_ret_data(request, &type1data); if (ret) goto error; ret = krb5_ret_stringz(request, &targetname); if (ret) goto error; ret = krb5_ret_uint32(request, &type1flags); if (ret) goto error; ndata.data = type2data.data; ndata.length = type2data.length; ret = heim_ntlm_decode_type2(&ndata, &type2); if (ret) goto error; kcm_log(10, "checking for ntlm mirror attack"); ret = check_ntlm_challage(type2.challenge); if (ret) { kcm_log(0, "ntlm mirror attack detected"); goto error; } /* if service name or case matching with domain, let pick the domain */ if (ntlm_domain_is_hostname(c->domain) || strcasecmp(domain, type2.targetname) == 0) { free(domain); domain = type2.targetname; if (domain == NULL) { ret = ENOMEM; goto error; } } else { free(domain); domain = c->domain; } type3.username = c->user; type3.flags = type2.flags; /* only allow what we negotiated ourself */ type3.flags &= type1flags; type3.targetname = domain; type3.ws = rk_UNCONST("workstation"); /* * Only do NTLM Version 1 if they force us */ if (gss_mo_get(GSS_NTLM_MECHANISM, GSS_C_NTLM_FORCE_V1, NULL)) { type = "v1"; if (type2.flags & NTLM_NEG_NTLM2_SESSION) { unsigned char nonce[8]; if (CCRandomCopyBytes(kCCRandomDefault, nonce, sizeof(nonce))) { 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; if (type3.flags & NTLM_NEG_KEYEX) { ret = heim_ntlm_build_ntlm1_master(c->nthash.data, c->nthash.length, &sessionkey, &type3.sessionkey); } else { ret = heim_ntlm_v1_base_session(c->nthash.data, c->nthash.length, &sessionkey); } if (ret) goto error; } else { unsigned char ntlmv2[16]; struct ntlm_targetinfo ti; static uint8_t zeros[16] = { 0 }; type = "v2"; /* verify infotarget */ ret = heim_ntlm_decode_targetinfo(&type2.targetinfo, 1, &ti); if (ret) goto error; if (ti.avflags & NTLM_TI_AV_FLAG_GUEST) flags |= KCM_NTLM_FLAG_AV_GUEST; if (ti.channel_bindings.data) free(ti.channel_bindings.data); if (ti.targetname) free(ti.targetname); /* * We are going to use MIC, tell server so it can reject the * authenticate if the mic is missing. */ ti.avflags |= NTLM_TI_AV_FLAG_MIC; ti.targetname = targetname; if (cb.length == 0) { ti.channel_bindings.data = zeros; ti.channel_bindings.length = sizeof(zeros); } else { kcm_log(10, "using channelbindings of size %lu", (unsigned long)cb.length); ti.channel_bindings.data = cb.data; ti.channel_bindings.length = cb.length; } ret = heim_ntlm_encode_targetinfo(&ti, TRUE, &tidata); ti.targetname = NULL; ti.channel_bindings.data = NULL; ti.channel_bindings.length = 0; heim_ntlm_free_targetinfo(&ti); if (ret) goto error; /* * Prefer NTLM_NEG_EXTENDED_SESSION over NTLM_NEG_LM_KEY as * decribed in 2.2.2.5. */ if (type3.flags & NTLM_NEG_NTLM2_SESSION) type3.flags &= ~NTLM_NEG_LM_KEY; if ((type3.flags & NTLM_NEG_LM_KEY) && gss_mo_get(GSS_NTLM_MECHANISM, GSS_C_NTLM_SUPPORT_LM2, NULL)) { ret = heim_ntlm_calculate_lm2(c->nthash.data, c->nthash.length, type3.username, domain, type2.challenge, ntlmv2, &type3.lm); } else { type3.lm.data = malloc(24); if (type3.lm.data == NULL) { ret = ENOMEM; } else { type3.lm.length = 24; memset(type3.lm.data, 0, type3.lm.length); } } if (ret) goto error; ret = heim_ntlm_calculate_ntlm2(c->nthash.data, c->nthash.length, type3.username, domain, type2.challenge, &tidata, ntlmv2, &type3.ntlm); if (ret) goto error; if (type3.flags & NTLM_NEG_KEYEX) { ret = heim_ntlm_build_ntlm2_master(ntlmv2, sizeof(ntlmv2), &type3.ntlm, &sessionkey, &type3.sessionkey); } else { ret = heim_ntlm_v2_base_session(ntlmv2, sizeof(ntlmv2), &type3.ntlm, &sessionkey); } memset(ntlmv2, 0, sizeof(ntlmv2)); if (ret) goto error; } ret = heim_ntlm_encode_type3(&type3, &ndata, &mic_offset); if (ret) goto error; if (ndata.length < CC_MD5_DIGEST_LENGTH) { ret = EINVAL; goto error; } if (mic_offset && mic_offset < ndata.length - CC_MD5_DIGEST_LENGTH) { CCHmacContext mic; uint8_t *p = (uint8_t *)ndata.data + mic_offset; CCHmacInit(&mic, kCCHmacAlgMD5, sessionkey.data, sessionkey.length); CCHmacUpdate(&mic, type1data.data, type1data.length); CCHmacUpdate(&mic, type2data.data, type2data.length); CCHmacUpdate(&mic, ndata.data, ndata.length); CCHmacFinal(&mic, p); } tempdata.data = ndata.data; tempdata.length = ndata.length; ret = krb5_store_data(response, tempdata); heim_ntlm_free_buf(&ndata); if (ret) goto error; ret = krb5_store_int32(response, flags); if (ret) goto error; tempdata.data = sessionkey.data; tempdata.length = sessionkey.length; ret = krb5_store_data(response, tempdata); if (ret) goto error; ret = krb5_store_string(response, c->user); if (ret) goto error; ret = krb5_store_string(response, domain); if (ret) goto error; ret = krb5_store_uint32(response, type3.flags); if (ret) goto error; heim_ntlm_unparse_flags(type2.flags, flagname, sizeof(flagname)); kcm_log(0, "ntlm %s request processed for %s\\%s flags: %s", type, domain, c->user, flagname); error: HEIMDAL_MUTEX_unlock(&cred_mutex); krb5_data_free(&cb); krb5_data_free(&type1data); krb5_data_free(&type2data); if (type3.lm.data) free(type3.lm.data); if (type3.ntlm.data) free(type3.ntlm.data); if (type3.sessionkey.data) free(type3.sessionkey.data); if (targetname) free(targetname); heim_ntlm_free_type2(&type2); heim_ntlm_free_buf(&sessionkey); heim_ntlm_free_buf(&tidata); free(user); return ret; #else return EINVAL; #endif } /* * { "GET_NTLM_UUID_LIST", NULL } * * reply: * 1 user domain uuid * 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; KCM_LOG_REQUEST(context, client, opcode); HEIMDAL_MUTEX_lock(&cred_mutex); for (c = ntlm_head; c != NULL; c = c->next) { if (c->type != KCM_NTLM_CRED || !kcm_is_same_session(client, c->uid, c->session)) continue; ret = krb5_store_uint32(response, 1); if (ret) goto out; ret = krb5_store_stringz(response, c->user); if (ret) goto out; ret = krb5_store_stringz(response, c->domain); if (ret) goto out; ret = krb5_storage_write(response, c->uuid, sizeof(c->uuid)); if (ret != sizeof(c->uuid)) { ret = ENOMEM; goto out; } } ret = krb5_store_uint32(response, 0); out: HEIMDAL_MUTEX_unlock(&cred_mutex); return ret; } static krb5_error_code kcm_op_add_scram_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; KCM_LOG_REQUEST(context, client, opcode); cred = create_cred(KCM_SCRAM_CRED); if (cred == NULL) return ENOMEM; ret = krb5_ret_stringz(request, &cred->user); if (ret) goto error; ret = krb5_ret_stringz(request, &cred->u.password); if (ret) goto error; HEIMDAL_MUTEX_lock(&cred_mutex); /* search for dups */ c = find_ntlm_cred(KCM_SCRAM_CRED, cred->user, NULL, client); if (c) { char *pw = c->u.password; c->u.password = cred->u.password; cred->u.password = pw; 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)); HEIMDAL_MUTEX_unlock(&cred_mutex); kcm_data_changed = 1; return 0; error: free_cred(cred); return ret; } static krb5_error_code kcm_op_have_scram_cred(krb5_context context, kcm_client *client, kcm_operation opcode, krb5_storage *request, krb5_storage *response) { struct kcm_ntlm_cred *c; char *user = NULL; krb5_error_code ret; KCM_LOG_REQUEST(context, client, opcode); ret = krb5_ret_stringz(request, &user); if (ret) return ret; HEIMDAL_MUTEX_lock(&cred_mutex); c = find_ntlm_cred(KCM_SCRAM_CRED, user, NULL, client); if (c == NULL) ret = ENOENT; if (c) (void)krb5_storage_write(response, c->uuid, sizeof(c->uuid)); HEIMDAL_MUTEX_unlock(&cred_mutex); free(user); return ret; } static krb5_error_code kcm_op_del_scram_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; krb5_error_code ret; KCM_LOG_REQUEST(context, client, opcode); ret = krb5_ret_stringz(request, &user); if (ret) return ret; HEIMDAL_MUTEX_lock(&cred_mutex); for (cp = &ntlm_head; *cp != NULL; cp = &(*cp)->next) { if ((*cp)->type == KCM_SCRAM_CRED && strcasecmp(user, (*cp)->user) == 0 && kcm_is_same_session(client, (*cp)->uid, (*cp)->session)) { c = *cp; *cp = c->next; free_cred(c); kcm_data_changed = 1; break; } } HEIMDAL_MUTEX_unlock(&cred_mutex); free(user); return ret; } /* * IN: * clientname: stringz * iterations: uint32_t * salt: krb5_data * c1: krb5_data * s1: krb5_data * c2noproof: krb5_data */ static krb5_error_code kcm_op_do_scram(krb5_context context, kcm_client *client, kcm_operation opcode, krb5_storage *request, krb5_storage *response) { #ifdef ENABLE_SCRAM heim_scram_data proof, server, client_key, stored, server_key, session_key; heim_scram_method method = HEIM_SCRAM_DIGEST_SHA1; krb5_data salt, c1, s1, c2noproof; struct kcm_ntlm_cred *c; krb5_error_code ret; uint32_t iterations; unsigned char *p, *q; char *user = NULL; size_t n; KCM_LOG_REQUEST(context, client, opcode); memset(&proof, 0, sizeof(proof)); memset(&server, 0, sizeof(server)); memset(&client_key, 0, sizeof(client_key)); memset(&stored, 0, sizeof(stored)); memset(&server_key, 0, sizeof(server_key)); memset(&session_key, 0, sizeof(session_key)); krb5_data_zero(&salt); krb5_data_zero(&c1); krb5_data_zero(&s1); krb5_data_zero(&c2noproof); HEIMDAL_MUTEX_lock(&cred_mutex); ret = krb5_ret_stringz(request, &user); if (ret) goto out; c = find_ntlm_cred(KCM_SCRAM_CRED, user, NULL, client); if (c == NULL) { ret = ENOENT; goto out; } ret = krb5_ret_uint32(request, &iterations); if (ret) goto out; ret = krb5_ret_data(request, &salt); if (ret) goto out; ret = krb5_ret_data(request, &c1); if (ret) goto out; ret = krb5_ret_data(request, &s1); if (ret) goto out; ret = krb5_ret_data(request, &c2noproof); if (ret) goto out; ret = heim_scram_stored_key(method, c->u.password, iterations, &salt, &client_key, &stored, &server_key); if (ret) goto out; ret = heim_scram_generate(method, &stored, &server_key, &c1, &s1, &c2noproof, &proof, &server); if (ret) goto out; ret = heim_scram_session_key(method, &stored, &client_key, &c1, &s1, &c2noproof, &session_key); if (ret) goto out; /* * Now client_key XOR proof */ p = proof.data; q = client_key.data; for (n = 0 ; n < client_key.length; n++) p[n] = p[n] ^ q[n]; ret = krb5_store_data(response, proof); if (ret) goto out; ret = krb5_store_data(response, server); if (ret) goto out; ret = krb5_store_data(response, session_key); if (ret) goto out; out: HEIMDAL_MUTEX_unlock(&cred_mutex); if (user) free(user); krb5_data_free(&salt); krb5_data_free(&c1); krb5_data_free(&s1); krb5_data_free(&c2noproof); heim_scram_data_free(&proof); heim_scram_data_free(&server); heim_scram_data_free(&client_key); heim_scram_data_free(&stored); heim_scram_data_free(&server_key); heim_scram_data_free(&session_key); return ret; #else return EINVAL; #endif } static krb5_error_code kcm_op_get_scram_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; KCM_LOG_REQUEST(context, client, opcode); for (c = ntlm_head; c != NULL; c = c->next) { if (c->type != KCM_SCRAM_CRED || !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_storage_write(response, c->uuid, sizeof(c->uuid)); if (ret != sizeof(c->uuid)) { ret = ENOMEM; return ret; } } return krb5_store_uint32(response, 0); } static krb5_error_code kcm_op_retain_cred(krb5_context context, kcm_client *client, kcm_operation opcode, krb5_storage *request, krb5_storage *response) { struct kcm_ntlm_cred *c; kcmuuid_t uuid; ssize_t sret; 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; } for (c = ntlm_head; c != NULL; c = c->next) { if (!kcm_is_same_session(client, c->uid, c->session)) continue; if (memcmp(uuid, c->uuid, sizeof(c->uuid)) == 0) { c->refcount++; kcm_data_changed = 1; return 0; } } return 0; } static krb5_error_code kcm_op_release_cred(krb5_context context, kcm_client *client, kcm_operation opcode, krb5_storage *request, krb5_storage *response) { struct kcm_ntlm_cred **cp; kcmuuid_t uuid; ssize_t sret; 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; } for (cp = &ntlm_head; *cp != NULL; cp = &(*cp)->next) { struct kcm_ntlm_cred *c = *cp; if (!kcm_is_same_session(client, c->uid, c->session)) continue; if (memcmp(uuid, c->uuid, sizeof(uuid)) == 0) { c->refcount--; if (c->refcount < 1) { *cp = c->next; free_cred(c); } kcm_data_changed = 1; return 0; } } return 0; } static krb5_error_code kcm_op_cred_label_get(krb5_context context, kcm_client *client, kcm_operation opcode, krb5_storage *request, krb5_storage *response) { struct kcm_ntlm_cred *c; krb5_error_code ret; heim_string_t s; char *label; kcmuuid_t uuid; ssize_t sret; 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 = krb5_ret_stringz(request, &label); if (ret) return ret; s = heim_string_create(label); free(label); for (c = ntlm_head; c != NULL; c = c->next) { if (!kcm_is_same_session(client, c->uid, c->session)) continue; if (memcmp(uuid, c->uuid, sizeof(c->uuid)) == 0) { heim_data_t d; d = heim_dict_copy_value(c->labels, s); if (d) { krb5_data data; data.length = d->length; data.data = d->data; krb5_store_data(response, data); heim_release(d); break; } } } heim_release(s); if (c == NULL) return ENOENT; return 0; } static krb5_error_code kcm_op_cred_label_set(krb5_context context, kcm_client *client, kcm_operation opcode, krb5_storage *request, krb5_storage *response) { struct kcm_ntlm_cred *c; kcmuuid_t uuid; krb5_data data; char *label = NULL; ssize_t sret; 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; } krb5_ret_stringz(request, &label); krb5_ret_data(request, &data); HEIMDAL_MUTEX_lock(&cred_mutex); for (c = ntlm_head; c != NULL; c = c->next) { if (!kcm_is_same_session(client, c->uid, c->session)) continue; if (memcmp(uuid, c->uuid, sizeof(uuid)) == 0) { heim_string_t s; s = heim_string_create(label); if (data.length) { heim_data_t d; d = heim_data_create(data.data, data.length); heim_dict_add_value(c->labels, s, d); heim_release(d); } else { heim_dict_delete_key(c->labels, s); } kcm_data_changed = 1; heim_release(s); break; } } HEIMDAL_MUTEX_unlock(&cred_mutex); krb5_data_free(&data); free(label); if (c == NULL) return ENOENT; return 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 }, { "RETAIN_KCRED", kcm_op_retain_kcred }, { "RELEASE_KCRED", kcm_op_release_kcred }, { "GET_UUID", kcm_op_get_uuid }, { "ADD_NTLM_CRED", kcm_op_add_ntlm_cred }, { "HAVE_NTLM_CRED", kcm_op_have_ntlm_cred }, { "SET_NTLM_CHALLEGE", kcm_op_add_ntlm_challenge }, { "DO_NTLM_AUTH", kcm_op_do_ntlm }, { "SET_NTLM_USER_LIST", kcm_op_get_ntlm_user_list }, { "ADD_SCRAM_CRED", kcm_op_add_scram_cred }, { "HAVE_SCRAM_CRED", kcm_op_have_scram_cred }, { "DEL_SCRAM_CRED", kcm_op_del_scram_cred }, { "DO_SCRAM_AUTH", kcm_op_do_scram }, { "GET_SCRAM_USER_LIST", kcm_op_get_scram_user_list }, { "DEL_CRED", kcm_op_del_cred }, { "RETAIN_CRED", kcm_op_retain_cred }, { "RELEASE_CRED", kcm_op_release_cred }, { "CRED_LABEL_GET", kcm_op_cred_label_get }, { "CRED_LABEL_SET", kcm_op_cred_label_set } }; 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; }