static krb5_error_code update_keytab_entry(krb5_context context, kcm_ccache ccache, krb5_enctype etype, char *cpn, char *spn, char *newpw, krb5_salt salt, unsigned kvno) { krb5_error_code ret; krb5_keytab_entry entry; krb5_data pw; memset(&entry, 0, sizeof(entry)); pw.data = (char *)newpw; pw.length = strlen(newpw); ret = krb5_string_to_key_data_salt(context, etype, pw, salt, &entry.keyblock); if (ret) { kcm_log(0, "String to key conversion failed for principal %s " "and etype %d: %s", cpn, etype, krb5_get_err_text(context, ret)); return ret; } if (spn == NULL) { ret = krb5_copy_principal(context, ccache->client, &entry.principal); if (ret) { kcm_log(0, "Failed to copy principal name %s: %s", cpn, krb5_get_err_text(context, ret)); return ret; } } else { ret = krb5_parse_name(context, spn, &entry.principal); if (ret) { kcm_log(0, "Failed to parse SPN alias %s: %s", spn, krb5_get_err_text(context, ret)); return ret; } } entry.vno = kvno; entry.timestamp = time(NULL); ret = krb5_kt_add_entry(context, ccache->key.keytab, &entry); if (ret) { kcm_log(0, "Failed to update keytab for principal %s " "and etype %d: %s", cpn, etype, krb5_get_err_text(context, ret)); } krb5_kt_free_entry(context, &entry); return ret; }
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; u_int16_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; } krb5_ret_int16(req_sp, &opcode); if (opcode >= sizeof(kcm_ops)/sizeof(kcm_ops[0])) { kcm_log(0, "Process %d: invalid operation code %d", client->pid, opcode); ret = KRB5_FCC_INTERNAL; goto out; } method = kcm_ops[opcode].method; /* seek past place for status code */ krb5_storage_seek(resp_sp, 4, SEEK_SET); ret = (*method)(context, client, opcode, req_sp, resp_sp); out: if (req_sp != NULL) { krb5_storage_free(req_sp); } krb5_storage_seek(resp_sp, 0, SEEK_SET); krb5_store_int32(resp_sp, ret); ret = krb5_storage_to_data(resp_sp, resp_data); krb5_storage_free(resp_sp); return ret; }
krb5_error_code kcm_ccache_destroy_client(krb5_context context, kcm_client *client, const char *name) { krb5_error_code ret; kcm_ccache ccache; const char *estr; ret = kcm_ccache_resolve(context, name, &ccache); if (ret) { estr = krb5_get_error_message(context, ret); kcm_log(1, "Failed to resolve cache %s: %s", name, estr); krb5_free_error_message(context, estr); return ret; } ret = kcm_access(context, client, KCM_OP_DESTROY, ccache); kcm_cleanup_events(context, ccache); kcm_release_ccache(context, ccache); if (ret) return ret; return kcm_ccache_destroy(context, name); }
krb5_error_code kcm_ccache_resolve_client(krb5_context context, kcm_client *client, kcm_operation opcode, const char *name, kcm_ccache *ccache) { krb5_error_code ret; const char *estr; ret = kcm_ccache_resolve(context, name, ccache); if (ret) { estr = krb5_get_error_message(context, ret); kcm_log(1, "Failed to resolve cache %s: %s", name, estr); krb5_free_error_message(context, estr); return ret; } ret = kcm_access(context, client, opcode, *ccache); if (ret) { ret = KRB5_FCC_NOFILE; /* don't disclose */ kcm_release_ccache(context, *ccache); } return ret; }
krb5_error_code kcm_run_events(krb5_context context, time_t now) { krb5_error_code ret; kcm_event **e; HEIMDAL_MUTEX_lock(&events_mutex); /* Only run event queue every N seconds */ if (now < last_run + KCM_EVENT_QUEUE_INTERVAL) { HEIMDAL_MUTEX_unlock(&events_mutex); return 0; } /* go through events list, fire and expire */ for (e = &events_head; *e != NULL; e = &(*e)->next) { if ((*e)->valid == 0) continue; if (now >= (*e)->fire_time) { ret = kcm_fire_event(context, e); if (ret) { kcm_log(1, "Could not fire event for cache %s: %s", (*e)->ccache->name, krb5_get_err_text(context, ret)); } } else if ((*e)->expire_time && now >= (*e)->expire_time) { ret = kcm_remove_event_internal(context, e); if (ret) { kcm_log(1, "Could not expire event for cache %s: %s", (*e)->ccache->name, krb5_get_err_text(context, ret)); } } if (*e == NULL) break; } last_run = now; HEIMDAL_MUTEX_unlock(&events_mutex); return 0; }
krb5_error_code kcm_debug_ccache(krb5_context context) { kcm_ccache p; for (p = ccache_head; p != NULL; p = p->next) { char *cpn = NULL, *spn = NULL; int ncreds = 0; struct kcm_creds *k; if ((p->flags & KCM_FLAGS_VALID) == 0) { kcm_log(7, "cache %08x: empty slot"); continue; } KCM_ASSERT_VALID(p); for (k = p->creds; k != NULL; k = k->next) ncreds++; if (p->client != NULL) krb5_unparse_name(context, p->client, &cpn); if (p->server != NULL) krb5_unparse_name(context, p->server, &spn); kcm_log(7, "cache %08x: name %s refcnt %d flags %04x mode %04o " "uid %d gid %d client %s server %s ncreds %d", p, p->name, p->refcnt, p->flags, p->mode, p->uid, p->gid, (cpn == NULL) ? "<none>" : cpn, (spn == NULL) ? "<none>" : spn, ncreds); if (cpn != NULL) free(cpn); if (spn != NULL) free(spn); } return 0; }
static void log_event(kcm_event *event, char *msg) { char fire_time[64], expire_time[64]; print_times(event->fire_time, fire_time); print_times(event->expire_time, expire_time); kcm_log(7, "%s event %08x: fire_time %s fire_count %d expire_time %s " "backoff_time %d action %s cache %s", msg, event, fire_time, event->fire_count, expire_time, event->backoff_time, action_strings[event->action], event->ccache->name); }
krb5_error_code kcm_ccache_destroy_client(krb5_context context, kcm_client *client, const char *name) { krb5_error_code ret; kcm_ccache ccache; ret = kcm_ccache_resolve_by_name(context, name, &ccache); if (ret) { kcm_log(1, "Failed to resolve cache %s", name); return ret; } ret = kcm_access(context, client, KCM_OP_DESTROY, ccache); kcm_release_ccache(context, ccache); if (ret) return ret; return kcm_ccache_destroy(context, name); }
/* * Setup default events for a new credential */ static krb5_error_code kcm_ccache_make_default_event(krb5_context context, kcm_event *event, krb5_creds *newcred) { krb5_error_code ret = 0; kcm_ccache ccache = event->ccache; event->fire_time = 0; event->expire_time = 0; event->backoff_time = KCM_EVENT_DEFAULT_BACKOFF_TIME; if (newcred == NULL) { /* no creds, must be acquire creds request */ if ((ccache->flags & KCM_MASK_KEY_PRESENT) == 0) { kcm_log(0, "Cannot acquire credentials without a key"); return KRB5_FCC_INTERNAL; } event->fire_time = time(NULL); /* right away */ event->action = KCM_EVENT_ACQUIRE_CREDS; } else if (is_primary_credential_p(context, ccache, newcred)) { if (newcred->flags.b.renewable) { event->action = KCM_EVENT_RENEW_CREDS; ccache->flags |= KCM_FLAGS_RENEWABLE; } else { if (ccache->flags & KCM_MASK_KEY_PRESENT) event->action = KCM_EVENT_ACQUIRE_CREDS; else event->action = KCM_EVENT_NONE; ccache->flags &= ~(KCM_FLAGS_RENEWABLE); } /* requeue with some slop factor */ event->fire_time = newcred->times.endtime - KCM_EVENT_QUEUE_INTERVAL; } else { event->action = KCM_EVENT_NONE; } return ret; }
krb5_error_code kcm_ccache_resolve_client(krb5_context context, kcm_client *client, kcm_operation opcode, const char *name, kcm_ccache *ccache) { krb5_error_code ret; ret = kcm_ccache_resolve_by_name(context, name, ccache); if (ret) { kcm_log(1, "Failed to resolve cache %s", name); return ret; } ret = kcm_access(context, client, opcode, *ccache); if (ret) { ret = KRB5_FCC_NOFILE; /* don't disclose */ kcm_release_ccache(context, *ccache); } return ret; }
static krb5_error_code change_pw_and_update_keytab(krb5_context context, kcm_ccache ccache) { char newpw[121]; krb5_error_code ret; unsigned kvno; krb5_salt salt; krb5_enctype *etypes = NULL; int i; char *cpn = NULL; char **spns = NULL; krb5_data_zero(&salt.saltvalue); ret = krb5_unparse_name(context, ccache->client, &cpn); if (ret) { kcm_log(0, "Failed to unparse name: %s", krb5_get_err_text(context, ret)); goto out; } ret = krb5_get_default_in_tkt_etypes(context, &etypes); if (ret) { kcm_log(0, "Failed to determine default encryption types: %s", krb5_get_err_text(context, ret)); goto out; } /* Generate a random password (there is no set keys protocol) */ generate_random_pw(context, newpw, sizeof(newpw)); /* Change it */ ret = change_pw(context, ccache, cpn, newpw); if (ret) goto out; /* Do an AS-REQ to determine salt and key version number */ ret = get_salt_and_kvno(context, ccache, etypes, cpn, newpw, &salt, &kvno); if (ret) { kcm_log(0, "Failed to determine salting principal for principal %s: %s", cpn, krb5_get_err_text(context, ret)); goto out; } /* Add canonical name */ ret = update_keytab_entries(context, ccache, etypes, cpn, NULL, newpw, salt, kvno); if (ret) goto out; /* Add SPN aliases, if any */ spns = krb5_config_get_strings(context, NULL, "kcm", "system_ccache", "spn_aliases", NULL); if (spns != NULL) { for (i = 0; spns[i] != NULL; i++) { ret = update_keytab_entries(context, ccache, etypes, cpn, spns[i], newpw, salt, kvno); if (ret) goto out; } } kcm_log(0, "Changed expired password for principal %s in cache %s", cpn, ccache->name); out: if (cpn != NULL) free(cpn); if (spns != NULL) krb5_config_free_strings(spns); if (etypes != NULL) free(etypes); krb5_free_salt(context, salt); memset(newpw, 0, sizeof(newpw)); return ret; }
krb5_error_code kcm_ccache_new_client(krb5_context context, kcm_client *client, const char *name, kcm_ccache *ccache_p) { krb5_error_code ret; kcm_ccache ccache; ret = kcm_ccache_resolve_by_name(context, name, &ccache); if (ret == 0) { if ((ccache->uid != client->uid) && !CLIENT_IS_ROOT(client)) return KRB5_FCC_PERM; } else if (ret != KRB5_FCC_NOFILE && !(CLIENT_IS_ROOT(client) && ret == KRB5_FCC_PERM)) { return ret; } if (ret == KRB5_FCC_NOFILE) { ret = kcm_ccache_new(context, name, &ccache); if (ret) { kcm_log(1, "Failed to initialize cache %s", name); return ret; } /* bind to current client */ ccache->uid = client->uid; ccache->session = client->session; /* * add notification when the session goes away, so we can * remove the credential */ kcm_session_add(client->session); } else { ret = kcm_zero_ccache_data(context, ccache); if (ret) { kcm_log(1, "Failed to empty cache %s", name); kcm_release_ccache(context, ccache); return ret; } heim_ipc_event_cancel(ccache->renew_event); heim_ipc_event_cancel(ccache->expire_event); } ret = kcm_access(context, client, KCM_OP_INITIALIZE, ccache); if (ret) { kcm_release_ccache(context, ccache); kcm_ccache_destroy(context, name); return ret; } /* * Finally, if the user is root and the cache was created under * another user's name, chown the cache to that user. */ if (CLIENT_IS_ROOT(client)) { unsigned long uid; int matches = sscanf(name,"%ld:",&uid); if (matches == 0) matches = sscanf(name,"%ld",&uid); if (matches == 1) { kcm_chown(context, client, ccache, (uid_t)uid); } } *ccache_p = ccache; return 0; }
krb5_error_code kcm_ccache_acquire(krb5_context context, kcm_ccache ccache, krb5_creds **credp) { krb5_error_code ret = 0; krb5_creds cred; krb5_const_realm realm; krb5_get_init_creds_opt opt; krb5_ccache_data ccdata; char *in_tkt_service = NULL; int done = 0; memset(&cred, 0, sizeof(cred)); KCM_ASSERT_VALID(ccache); /* We need a cached key or keytab to acquire credentials */ if (ccache->flags & KCM_FLAGS_USE_CACHED_KEY) { if (ccache->key.keyblock.keyvalue.length == 0) krb5_abortx(context, "kcm_ccache_acquire: KCM_FLAGS_USE_CACHED_KEY without key"); } else if (ccache->flags & KCM_FLAGS_USE_KEYTAB) { if (ccache->key.keytab == NULL) krb5_abortx(context, "kcm_ccache_acquire: KCM_FLAGS_USE_KEYTAB without keytab"); } else { kcm_log(0, "Cannot acquire initial credentials for cache %s without key", ccache->name); return KRB5_FCC_INTERNAL; } HEIMDAL_MUTEX_lock(&ccache->mutex); /* Fake up an internal ccache */ kcm_internal_ccache(context, ccache, &ccdata); /* Now, actually acquire the creds */ if (ccache->server != NULL) { ret = krb5_unparse_name(context, ccache->server, &in_tkt_service); if (ret) { kcm_log(0, "Failed to unparse service principal name for cache %s: %s", ccache->name, krb5_get_err_text(context, ret)); return ret; } } realm = krb5_principal_get_realm(context, ccache->client); krb5_get_init_creds_opt_init(&opt); krb5_get_init_creds_opt_set_default_flags(context, "kcm", realm, &opt); if (ccache->tkt_life != 0) krb5_get_init_creds_opt_set_tkt_life(&opt, ccache->tkt_life); if (ccache->renew_life != 0) krb5_get_init_creds_opt_set_renew_life(&opt, ccache->renew_life); if (ccache->flags & KCM_FLAGS_USE_CACHED_KEY) { ret = krb5_get_init_creds_keyblock(context, &cred, ccache->client, &ccache->key.keyblock, 0, in_tkt_service, &opt); } else { /* loosely based on lib/krb5/init_creds_pw.c */ while (!done) { ret = krb5_get_init_creds_keytab(context, &cred, ccache->client, ccache->key.keytab, 0, in_tkt_service, &opt); switch (ret) { case KRB5KDC_ERR_KEY_EXPIRED: if (in_tkt_service != NULL && strcmp(in_tkt_service, "kadmin/changepw") == 0) { goto out; } ret = change_pw_and_update_keytab(context, ccache); if (ret) goto out; break; case 0: default: done = 1; break; } } } if (ret) { kcm_log(0, "Failed to acquire credentials for cache %s: %s", ccache->name, krb5_get_err_text(context, ret)); if (in_tkt_service != NULL) free(in_tkt_service); goto out; } if (in_tkt_service != NULL) free(in_tkt_service); /* Swap them in */ kcm_ccache_remove_creds_internal(context, ccache); ret = kcm_ccache_store_cred_internal(context, ccache, &cred, 0, credp); if (ret) { kcm_log(0, "Failed to store credentials for cache %s: %s", ccache->name, krb5_get_err_text(context, ret)); krb5_free_cred_contents(context, &cred); goto out; } out: HEIMDAL_MUTEX_unlock(&ccache->mutex); return ret; }
static krb5_error_code change_pw(krb5_context context, kcm_ccache ccache, char *cpn, char *newpw) { krb5_error_code ret; krb5_creds cpw_cred; int result_code; krb5_data result_code_string; krb5_data result_string; krb5_get_init_creds_opt options; memset(&cpw_cred, 0, sizeof(cpw_cred)); krb5_get_init_creds_opt_init(&options); krb5_get_init_creds_opt_set_tkt_life(&options, 60); krb5_get_init_creds_opt_set_forwardable(&options, FALSE); krb5_get_init_creds_opt_set_proxiable(&options, FALSE); krb5_data_zero(&result_code_string); krb5_data_zero(&result_string); ret = krb5_get_init_creds_keytab(context, &cpw_cred, ccache->client, ccache->key.keytab, 0, "kadmin/changepw", &options); if (ret) { kcm_log(0, "Failed to acquire password change credentials " "for principal %s: %s", cpn, krb5_get_err_text(context, ret)); goto out; } ret = krb5_set_password(context, &cpw_cred, newpw, ccache->client, &result_code, &result_code_string, &result_string); if (ret) { kcm_log(0, "Failed to change password for principal %s: %s", cpn, krb5_get_err_text(context, ret)); goto out; } if (result_code) { kcm_log(0, "Failed to change password for principal %s: %.*s", cpn, (int)result_string.length, result_string.length > 0 ? (char *)result_string.data : ""); goto out; } out: krb5_data_free(&result_string); krb5_data_free(&result_code_string); krb5_free_cred_contents(context, &cpw_cred); return ret; }
static krb5_error_code get_salt_and_kvno(krb5_context context, kcm_ccache ccache, krb5_enctype *etypes, char *cpn, char *newpw, krb5_salt *salt, unsigned *kvno) { krb5_error_code ret; krb5_creds creds; krb5_ccache_data ccdata; krb5_flags options = 0; krb5_kdc_rep reply; struct kcm_keyseed_data s; memset(&creds, 0, sizeof(creds)); memset(&reply, 0, sizeof(reply)); s.password = NULL; s.salt.salttype = (int)ETYPE_NULL; krb5_data_zero(&s.salt.saltvalue); *kvno = 0; kcm_internal_ccache(context, ccache, &ccdata); s.password = newpw; /* Do an AS-REQ to determine salt and key version number */ ret = krb5_copy_principal(context, ccache->client, &creds.client); if (ret) return ret; /* Yes, get a ticket to ourselves */ ret = krb5_copy_principal(context, ccache->client, &creds.server); if (ret) { krb5_free_principal(context, creds.client); return ret; } ret = krb5_get_in_tkt(context, options, NULL, etypes, NULL, kcm_password_key_proc, &s, NULL, NULL, &creds, &ccdata, &reply); if (ret) { kcm_log(0, "Failed to get self ticket for principal %s: %s", cpn, krb5_get_err_text(context, ret)); krb5_free_salt(context, s.salt); } else { *salt = s.salt; /* retrieve stashed salt */ if (reply.kdc_rep.enc_part.kvno != NULL) *kvno = *(reply.kdc_rep.enc_part.kvno); } /* ccache may have been modified but it will get trashed anyway */ krb5_free_cred_contents(context, &creds); krb5_free_kdc_rep(context, &reply); return ret; }
krb5_error_code kcm_ccache_refresh(krb5_context context, kcm_ccache ccache, krb5_creds **credp) { krb5_error_code ret; krb5_creds in, *out; krb5_kdc_flags flags; krb5_const_realm realm; krb5_ccache_data ccdata; const char *estr; memset(&in, 0, sizeof(in)); KCM_ASSERT_VALID(ccache); if (ccache->client == NULL) { /* no primary principal */ kcm_log(0, "Refresh credentials requested but no client principal"); return KRB5_CC_NOTFOUND; } HEIMDAL_MUTEX_lock(&ccache->mutex); /* Fake up an internal ccache */ kcm_internal_ccache(context, ccache, &ccdata); /* Find principal */ in.client = ccache->client; if (ccache->server != NULL) { ret = krb5_copy_principal(context, ccache->server, &in.server); if (ret) { estr = krb5_get_error_message(context, ret); kcm_log(0, "Failed to copy service principal: %s", estr); krb5_free_error_message(context, estr); goto out; } } else { realm = krb5_principal_get_realm(context, in.client); ret = krb5_make_principal(context, &in.server, realm, KRB5_TGS_NAME, realm, NULL); if (ret) { estr = krb5_get_error_message(context, ret); kcm_log(0, "Failed to make TGS principal for realm %s: %s", realm, estr); krb5_free_error_message(context, estr); goto out; } } if (ccache->tkt_life) in.times.endtime = time(NULL) + ccache->tkt_life; if (ccache->renew_life) in.times.renew_till = time(NULL) + ccache->renew_life; flags.i = 0; flags.b.renewable = TRUE; flags.b.renew = TRUE; ret = krb5_get_kdc_cred(context, &ccdata, flags, NULL, NULL, &in, &out); if (ret) { estr = krb5_get_error_message(context, ret); kcm_log(0, "Failed to renew credentials for cache %s: %s", ccache->name, estr); krb5_free_error_message(context, estr); goto out; } /* Swap them in */ kcm_ccache_remove_creds_internal(context, ccache); ret = kcm_ccache_store_cred_internal(context, ccache, out, 0, credp); if (ret) { estr = krb5_get_error_message(context, ret); kcm_log(0, "Failed to store credentials for cache %s: %s", ccache->name, estr); krb5_free_error_message(context, estr); krb5_free_creds(context, out); goto out; } free(out); /* but not contents */ out: HEIMDAL_MUTEX_unlock(&ccache->mutex); return ret; }
void kcm_session_add(pid_t session_id) { kcm_log(1, "monitor session: %d\n", session_id); }
krb5_error_code kcm_access(krb5_context context, kcm_client *client, kcm_operation opcode, kcm_ccache ccache) { int read_p = 0; int write_p = 0; uint16_t mask; krb5_error_code ret; KCM_ASSERT_VALID(ccache); switch (opcode) { case KCM_OP_INITIALIZE: case KCM_OP_DESTROY: case KCM_OP_STORE: case KCM_OP_REMOVE_CRED: case KCM_OP_SET_FLAGS: case KCM_OP_CHOWN: case KCM_OP_CHMOD: case KCM_OP_GET_INITIAL_TICKET: case KCM_OP_GET_TICKET: write_p = 1; read_p = 0; break; case KCM_OP_NOOP: case KCM_OP_GET_NAME: case KCM_OP_RESOLVE: case KCM_OP_GEN_NEW: case KCM_OP_RETRIEVE: case KCM_OP_GET_PRINCIPAL: case KCM_OP_GET_FIRST: case KCM_OP_GET_NEXT: case KCM_OP_END_GET: case KCM_OP_MAX: write_p = 0; read_p = 1; break; } if (ccache->flags & KCM_FLAGS_OWNER_IS_SYSTEM) { /* System caches cannot be reinitialized or destroyed by users */ if (opcode == KCM_OP_INITIALIZE || opcode == KCM_OP_DESTROY || opcode == KCM_OP_REMOVE_CRED) { ret = KRB5_FCC_PERM; goto out; } /* Let root always read system caches */ if (client->uid == 0) { ret = 0; goto out; } } mask = 0; /* Root may do whatever they like */ if (client->uid == ccache->uid || CLIENT_IS_ROOT(client)) { if (read_p) mask |= S_IRUSR; if (write_p) mask |= S_IWUSR; } else if (client->gid == ccache->gid || CLIENT_IS_ROOT(client)) { if (read_p) mask |= S_IRGRP; if (write_p) mask |= S_IWGRP; } else { if (read_p) mask |= S_IROTH; if (write_p) mask |= S_IWOTH; } ret = ((ccache->mode & mask) == mask) ? 0 : KRB5_FCC_PERM; out: if (ret) { kcm_log(2, "Process %d is not permitted to call %s on cache %s", client->pid, kcm_op2string(opcode), ccache->name); } return ret; }
krb5_error_code kcm_access(krb5_context context, kcm_client *client, kcm_operation opcode, kcm_ccache ccache) { int read_p = 0; int write_p = 0; uint16_t mask; krb5_error_code ret; KCM_ASSERT_VALID(ccache); switch (opcode) { case KCM_OP_INITIALIZE: case KCM_OP_DESTROY: case KCM_OP_STORE: case KCM_OP_REMOVE_CRED: case KCM_OP_SET_FLAGS: case KCM_OP_CHOWN: case KCM_OP_CHMOD: case KCM_OP_GET_INITIAL_TICKET: case KCM_OP_GET_TICKET: case KCM_OP_MOVE_CACHE: case KCM_OP_SET_DEFAULT_CACHE: case KCM_OP_SET_KDC_OFFSET: write_p = 1; read_p = 0; break; case KCM_OP_NOOP: case KCM_OP_GET_NAME: case KCM_OP_RESOLVE: case KCM_OP_GEN_NEW: case KCM_OP_RETRIEVE: case KCM_OP_GET_PRINCIPAL: case KCM_OP_GET_CRED_UUID_LIST: case KCM_OP_GET_CRED_BY_UUID: case KCM_OP_GET_CACHE_UUID_LIST: case KCM_OP_GET_CACHE_BY_UUID: case KCM_OP_GET_DEFAULT_CACHE: case KCM_OP_GET_KDC_OFFSET: write_p = 0; read_p = 1; break; default: ret = KRB5_FCC_PERM; goto out; } if (ccache->flags & KCM_FLAGS_OWNER_IS_SYSTEM) { /* System caches cannot be reinitialized or destroyed by users */ if (opcode == KCM_OP_INITIALIZE || opcode == KCM_OP_DESTROY || opcode == KCM_OP_REMOVE_CRED || opcode == KCM_OP_MOVE_CACHE) { ret = KRB5_FCC_PERM; goto out; } /* Let root always read system caches */ if (CLIENT_IS_ROOT(client)) { ret = 0; goto out; } } /* start out with "other" mask */ mask = S_IROTH|S_IWOTH; /* root can do anything */ if (CLIENT_IS_ROOT(client)) { if (read_p) mask |= S_IRUSR|S_IRGRP|S_IROTH; if (write_p) mask |= S_IWUSR|S_IWGRP|S_IWOTH; } /* same session same as owner */ if (kcm_is_same_session(client, ccache->uid, ccache->session)) { if (read_p) mask |= S_IROTH; if (write_p) mask |= S_IWOTH; } /* owner */ if (client->uid == ccache->uid) { if (read_p) mask |= S_IRUSR; if (write_p) mask |= S_IWUSR; } /* group */ if (client->gid == ccache->gid) { if (read_p) mask |= S_IRGRP; if (write_p) mask |= S_IWGRP; } ret = (ccache->mode & mask) ? 0 : KRB5_FCC_PERM; out: if (ret) { kcm_log(2, "Process %d is not permitted to call %s on cache %s", client->pid, kcm_op2string(opcode), ccache->name); } return ret; }
krb5_error_code kcm_ccache_acquire(krb5_context context, kcm_ccache ccache, time_t *expire) { krb5_error_code ret = 0; krb5_creds cred; krb5_const_realm realm; krb5_get_init_creds_opt *opt = NULL; krb5_ccache_data ccdata; char *in_tkt_service = NULL; *expire = 0; memset(&cred, 0, sizeof(cred)); KCM_ASSERT_VALID(ccache); /* We need a cached key or keytab to acquire credentials */ if (ccache->flags & KCM_FLAGS_USE_PASSWORD) { if (ccache->password == NULL) krb5_abortx(context, "kcm_ccache_acquire: KCM_FLAGS_USE_PASSWORD without password"); } else if (ccache->flags & KCM_FLAGS_USE_KEYTAB) { if (ccache->keytab == NULL) krb5_abortx(context, "kcm_ccache_acquire: KCM_FLAGS_USE_KEYTAB without keytab"); } else { kcm_log(0, "Cannot acquire initial credentials for cache %s without key", ccache->name); return KRB5_FCC_INTERNAL; } /* Fake up an internal ccache */ kcm_internal_ccache(context, ccache, &ccdata); /* Now, actually acquire the creds */ if (ccache->server != NULL) { ret = krb5_unparse_name(context, ccache->server, &in_tkt_service); if (ret) { kcm_log(0, "Failed to unparse service name for cache %s", ccache->name); return ret; } } realm = krb5_principal_get_realm(context, ccache->client); ret = krb5_get_init_creds_opt_alloc(context, &opt); if (ret) goto out; krb5_get_init_creds_opt_set_default_flags(context, "kcm", realm, opt); if (ccache->tkt_life != 0) krb5_get_init_creds_opt_set_tkt_life(opt, ccache->tkt_life); if (ccache->renew_life != 0) krb5_get_init_creds_opt_set_renew_life(opt, ccache->renew_life); krb5_get_init_creds_opt_set_forwardable(opt, 1); if (ccache->flags & KCM_FLAGS_USE_PASSWORD) { ret = krb5_get_init_creds_password(context, &cred, ccache->client, ccache->password, NULL, NULL, 0, in_tkt_service, opt); } else { ret = krb5_get_init_creds_keytab(context, &cred, ccache->client, ccache->keytab, 0, in_tkt_service, opt); } if (ret) { const char *msg = krb5_get_error_message(context, ret); kcm_log(0, "Failed to acquire credentials for cache %s: %s", ccache->name, msg); krb5_free_error_message(context, msg); if (in_tkt_service != NULL) free(in_tkt_service); goto out; } if (in_tkt_service != NULL) free(in_tkt_service); /* Swap them in */ kcm_ccache_remove_creds_internal(context, ccache); ret = kcm_ccache_store_cred_internal(context, ccache, &cred, NULL, 0); if (ret) { const char *msg = krb5_get_error_message(context, ret); kcm_log(0, "Failed to store credentials for cache %s: %s", ccache->name, msg); krb5_free_error_message(context, msg); krb5_free_cred_contents(context, &cred); goto out; } *expire = cred.times.endtime; out: if (opt) krb5_get_init_creds_opt_free(context, opt); return ret; }
krb5_error_code kcm_ccache_new_client(krb5_context context, kcm_client *client, const char *name, kcm_ccache *ccache_p) { krb5_error_code ret; kcm_ccache ccache; /* We insist the ccache name starts with UID or UID: */ if (name_constraints != 0) { char prefix[64]; size_t prefix_len; int bad = 1; snprintf(prefix, sizeof(prefix), "%ld:", (long)client->uid); prefix_len = strlen(prefix); if (strncmp(name, prefix, prefix_len) == 0) bad = 0; else { prefix[prefix_len - 1] = '\0'; if (strcmp(name, prefix) == 0) bad = 0; } /* Allow root to create badly-named ccaches */ if (bad && !CLIENT_IS_ROOT(client)) return KRB5_CC_BADNAME; } ret = kcm_ccache_resolve(context, name, &ccache); if (ret == 0) { if ((ccache->uid != client->uid || ccache->gid != client->gid) && !CLIENT_IS_ROOT(client)) return KRB5_FCC_PERM; } else if (ret != KRB5_FCC_NOFILE && !(CLIENT_IS_ROOT(client) && ret == KRB5_FCC_PERM)) { return ret; } if (ret == KRB5_FCC_NOFILE) { ret = kcm_ccache_new(context, name, &ccache); if (ret) { kcm_log(1, "Failed to initialize cache %s: %s", name, krb5_get_err_text(context, ret)); return ret; } /* bind to current client */ ccache->uid = client->uid; ccache->gid = client->gid; ccache->session = client->session; } else { ret = kcm_zero_ccache_data(context, ccache); if (ret) { kcm_log(1, "Failed to empty cache %s: %s", name, krb5_get_err_text(context, ret)); kcm_release_ccache(context, ccache); return ret; } kcm_cleanup_events(context, ccache); } ret = kcm_access(context, client, KCM_OP_INITIALIZE, ccache); if (ret) { kcm_release_ccache(context, ccache); kcm_ccache_destroy(context, name); return ret; } /* * Finally, if the user is root and the cache was created under * another user's name, chown the cache to that user and their * default gid. */ if (CLIENT_IS_ROOT(client)) { unsigned long uid; int matches = sscanf(name,"%ld:",&uid); if (matches == 0) matches = sscanf(name,"%ld",&uid); if (matches == 1) { struct passwd *pwd = getpwuid(uid); if (pwd != NULL) { gid_t gid = pwd->pw_gid; kcm_chown(context, client, ccache, uid, gid); } } } *ccache_p = ccache; return 0; }
static krb5_error_code kcm_fire_event(krb5_context context, kcm_event **e) { kcm_event *event; krb5_error_code ret; krb5_creds *credp = NULL; int oneshot = 1; event = *e; switch (event->action) { case KCM_EVENT_ACQUIRE_CREDS: ret = kcm_ccache_acquire(context, event->ccache, &credp); oneshot = 0; break; case KCM_EVENT_RENEW_CREDS: ret = kcm_ccache_refresh(context, event->ccache, &credp); if (ret == KRB5KRB_AP_ERR_TKT_EXPIRED) { ret = kcm_ccache_acquire(context, event->ccache, &credp); } oneshot = 0; break; case KCM_EVENT_DESTROY_CREDS: ret = kcm_ccache_destroy(context, event->ccache->name); break; case KCM_EVENT_DESTROY_EMPTY_CACHE: ret = kcm_ccache_destroy_if_empty(context, event->ccache); break; default: ret = KRB5_FCC_INTERNAL; break; } event->fire_count++; if (ret) { /* Reschedule failed event for another time */ event->fire_time += event->backoff_time; if (event->backoff_time < KCM_EVENT_MAX_BACKOFF_TIME) event->backoff_time *= 2; /* Remove it if it would never get executed */ if (event->expire_time && event->fire_time > event->expire_time) kcm_remove_event_internal(context, e); } else { if (!oneshot) { char *cpn; if (krb5_unparse_name(context, event->ccache->client, &cpn)) cpn = NULL; kcm_log(0, "%s credentials in cache %s for principal %s", (event->action == KCM_EVENT_ACQUIRE_CREDS) ? "Acquired" : "Renewed", event->ccache->name, (cpn != NULL) ? cpn : "<none>"); if (cpn != NULL) free(cpn); /* Succeeded, but possibly replaced with another event */ ret = kcm_ccache_make_default_event(context, event, credp); if (ret || event->action == KCM_EVENT_NONE) oneshot = 1; else log_event(event, "requeuing"); } if (oneshot) kcm_remove_event_internal(context, e); } return ret; }