/* Construct a plugin mapping object. path may be NULL (for a built-in * module), or may be relative to the plugin base directory. */ static krb5_error_code make_plugin_mapping(krb5_context context, const char *name, size_t namelen, const char *path, krb5_plugin_initvt_fn module, struct plugin_mapping **map_out) { krb5_error_code ret; struct plugin_mapping *map = NULL; /* Create the mapping entry. */ map = k5alloc(sizeof(*map), &ret); if (map == NULL) return ret; map->modname = k5memdup0(name, namelen, &ret); if (map->modname == NULL) goto oom; if (path != NULL) { if (k5_path_join(context->plugin_base_dir, path, &map->dyn_path)) goto oom; } map->module = module; *map_out = map; return 0; oom: free_plugin_mapping(map); return ENOMEM; }
/* * Get realm list from "capaths" section of the profile. Deliberately * returns success but leaves VALS null if profile_get_values() fails * by not finding anything. */ static krb5_error_code rtree_capath_vals(krb5_context context, const krb5_data *client, const krb5_data *server, char ***vals) { krb5_error_code retval = 0; /* null-terminated realm names */ char *clientz = NULL, *serverz = NULL; const char *key[4]; *vals = NULL; clientz = k5memdup0(client->data, client->length, &retval); if (clientz == NULL) goto error; serverz = k5memdup0(server->data, server->length, &retval); if (serverz == NULL) goto error; key[0] = "capaths"; key[1] = clientz; key[2] = serverz; key[3] = NULL; retval = profile_get_values(context->profile, key, vals); switch (retval) { case PROF_NO_SECTION: case PROF_NO_RELATION: /* * Not found; don't return an error. */ retval = 0; break; default: break; } error: free(clientz); free(serverz); return retval; }
OM_uint32 KRB5_CALLCONV krb5_gss_import_cred(OM_uint32 *minor_status, gss_buffer_t token, gss_cred_id_t *cred_handle) { OM_uint32 status = GSS_S_COMPLETE; krb5_context context; krb5_error_code ret; krb5_gss_cred_id_t cred; k5_json_value v = NULL; k5_json_array array; k5_json_string str; char *copy = NULL; ret = krb5_gss_init_context(&context); if (ret) { *minor_status = ret; return GSS_S_FAILURE; } /* Decode token. */ copy = k5memdup0(token->value, token->length, &ret); if (copy == NULL) { status = GSS_S_FAILURE; *minor_status = ret; goto cleanup; } if (k5_json_decode(copy, &v)) goto invalid; /* Decode the CRED_EXPORT_MAGIC array wrapper. */ if (k5_json_get_tid(v) != K5_JSON_TID_ARRAY) goto invalid; array = v; if (k5_json_array_length(array) != 2) goto invalid; str = check_element(array, 0, K5_JSON_TID_STRING); if (str == NULL || strcmp(k5_json_string_utf8(str), CRED_EXPORT_MAGIC) != 0) goto invalid; if (json_to_kgcred(context, k5_json_array_get(array, 1), &cred)) goto invalid; *cred_handle = (gss_cred_id_t)cred; cleanup: free(copy); k5_json_release(v); krb5_free_context(context); return status; invalid: status = GSS_S_DEFECTIVE_TOKEN; goto cleanup; }
/* * Hash extension records have the format: * client = <empty string> * server = HASH:<msghash> <clientlen>:<client> <serverlen>:<server> * Spaces in the client and server string are represented with * with backslashes. Client and server lengths are represented in * ASCII decimal (which is different from the 32-bit binary we use * elsewhere in the replay cache). * * On parse failure, we leave the record unmodified. */ static krb5_error_code check_hash_extension(krb5_donot_replay *rep) { char *msghash = NULL, *client = NULL, *server = NULL, *str, *end; krb5_error_code retval = 0; /* Check if this appears to match the hash extension format. */ if (*rep->client) return 0; if (strncmp(rep->server, "HASH:", 5) != 0) return 0; /* Parse out the message hash. */ str = rep->server + 5; end = strchr(str, ' '); if (!end) return 0; msghash = k5memdup0(str, end - str, &retval); if (!msghash) return KRB5_RC_MALLOC; str = end + 1; /* Parse out the client and server. */ retval = parse_counted_string(&str, &client); if (retval != 0 || client == NULL) goto error; if (*str != ' ') goto error; str++; retval = parse_counted_string(&str, &server); if (retval != 0 || server == NULL) goto error; if (*str) goto error; free(rep->client); free(rep->server); rep->client = client; rep->server = server; rep->msghash = msghash; return 0; error: if (msghash) free(msghash); if (client) free(client); if (server) free(server); return retval; }
krb5_error_code KRB5_CALLCONV krb5_get_fallback_host_realm(krb5_context context, krb5_data *hdata, char ***realms_out) { krb5_error_code ret; struct hostrealm_module_handle **hp; char **realms, *defrealm, *host, cleanname[1024]; *realms_out = NULL; /* Convert hdata into a string and clean it. */ host = k5memdup0(hdata->data, hdata->length, &ret); if (host == NULL) return ret; ret = k5_clean_hostname(context, host, cleanname, sizeof(cleanname)); free(host); if (ret) return ret; if (context->hostrealm_handles == NULL) { ret = load_hostrealm_modules(context); if (ret) return ret; } /* Give each module a chance to determine the fallback realms. */ for (hp = context->hostrealm_handles; *hp != NULL; hp++) { ret = fallback_realm(context, *hp, cleanname, &realms); if (ret == 0) { ret = copy_list(realms, realms_out); free_list(context, *hp, realms); return ret; } else if (ret != KRB5_PLUGIN_NO_HANDLE) { return ret; } } /* Return a list containing the default realm. */ ret = krb5_get_default_realm(context, &defrealm); if (ret) return ret; ret = k5_make_realmlist(defrealm, realms_out); krb5_free_default_realm(context, defrealm); return ret; }
/* Create a fake error reply. */ static krb5_error_code test_send_error(krb5_context context, void *data, const krb5_data *realm, const krb5_data *message, krb5_data **new_message_out, krb5_data **reply_out) { krb5_error_code ret; krb5_error err; krb5_principal client, server; char *realm_str, *princ_str; int r; realm_str = k5memdup0(realm->data, realm->length, &ret); check(ret); r = asprintf(&princ_str, "invalid@%s", realm_str); assert(r > 0); check(krb5_parse_name(ctx, princ_str, &client)); free(princ_str); r = asprintf(&princ_str, "krbtgt@%s", realm_str); assert(r > 0); check(krb5_parse_name(ctx, princ_str, &server)); free(princ_str); free(realm_str); err.magic = KV5M_ERROR; err.ctime = 1971196337; err.cusec = 0; err.susec = 97008; err.stime = 1458219390; err.error = 6; err.client = client; err.server = server; err.text = string2data("CLIENT_NOT_FOUND"); err.e_data = empty_data(); check(encode_krb5_error(&err, reply_out)); krb5_free_principal(ctx, client); krb5_free_principal(ctx, server); return 0; }
static krb5_error_code get_db_opt(const char *input, char **opt_out, char **val_out) { krb5_error_code ret; const char *pos; char *opt, *val = NULL; size_t len; *opt_out = *val_out = NULL; pos = strchr(input, '='); if (pos == NULL) { opt = strdup(input); if (opt == NULL) return ENOMEM; } else { len = pos - input; /* Ignore trailing spaces. */ while (len > 0 && isspace((unsigned char)input[len - 1])) len--; opt = k5memdup0(input, len, &ret); if (opt == NULL) return ret; pos++; /* Move past '='. */ while (isspace(*pos)) /* Ignore leading spaces. */ pos++; if (*pos != '\0') { val = strdup(pos); if (val == NULL) { free(opt); return ENOMEM; } } } *opt_out = opt; *val_out = val; return 0; }
/* Set up conn->http.tls. Return true on success. */ static krb5_boolean setup_tls(krb5_context context, const krb5_data *realm, struct conn_state *conn, struct select_state *selstate) { krb5_error_code ret; krb5_boolean ok = FALSE; char **anchors = NULL, *realmstr = NULL; const char *names[4]; if (init_tls_vtable(context) != 0 || context->tls->setup == NULL) return FALSE; realmstr = k5memdup0(realm->data, realm->length, &ret); if (realmstr == NULL) goto cleanup; /* Load the configured anchors. */ names[0] = KRB5_CONF_REALMS; names[1] = realmstr; names[2] = KRB5_CONF_HTTP_ANCHORS; names[3] = NULL; ret = profile_get_values(context->profile, names, &anchors); if (ret != 0 && ret != PROF_NO_RELATION) goto cleanup; if (context->tls->setup(context, conn->fd, conn->http.servername, anchors, &conn->http.tls) != 0) { TRACE_SENDTO_KDC_HTTPS_ERROR_CONNECT(context, &conn->addr); goto cleanup; } ok = TRUE; cleanup: free(realmstr); profile_free_list(anchors); return ok; }
/* Scan tl for a value of the given type and return it in allocated memory. * For KDB_TL_LINKDN, return a list of all values found. */ static krb5_error_code decode_tl_data(krb5_tl_data *tl, int type, void **data_out) { krb5_error_code ret; const unsigned char *ptr, *end; uint16_t len; size_t linkcount = 0, i; char **dnlist = NULL, **newlist; int *intptr; *data_out = NULL; /* Find the first matching subfield or return ENOENT. For KDB_TL_LINKDN, * keep iterating after finding a match as it may be repeated. */ ptr = tl->tl_data_contents; end = ptr + tl->tl_data_length; for (;;) { if (end - ptr < 3) break; len = load_16_be(ptr + 1); if (len > (end - ptr) - 3) break; if (*ptr != type) { ptr += 3 + len; continue; } ptr += 3; switch (type) { case KDB_TL_PRINCCOUNT: case KDB_TL_PRINCTYPE: case KDB_TL_MASK: if (len != 2) return EINVAL; intptr = malloc(sizeof(int)); if (intptr == NULL) return ENOMEM; *intptr = load_16_be(ptr); *data_out = intptr; return 0; case KDB_TL_USERDN: *data_out = k5memdup0(ptr, len, &ret); return ret; case KDB_TL_LINKDN: newlist = realloc(dnlist, (linkcount + 2) * sizeof(char *)); if (newlist == NULL) goto oom; dnlist = newlist; dnlist[linkcount] = k5memdup0(ptr, len, &ret); if (dnlist[linkcount] == NULL) goto oom; dnlist[linkcount + 1] = NULL; linkcount++; break; } ptr += len; } if (type != KDB_TL_LINKDN || dnlist == NULL) return ENOENT; *data_out = dnlist; return 0; oom: for (i = 0; i < linkcount; i++) free(dnlist[i]); free(dnlist); return ENOMEM; }
/* Extract a name from policy_dn, which must be directly under the realm * container. */ krb5_error_code krb5_ldap_policydn_to_name(krb5_context context, const char *policy_dn, char **name_out) { size_t len1, len2, plen; krb5_error_code ret; kdb5_dal_handle *dal_handle; krb5_ldap_context *ldap_context; const char *realmdn; *name_out = NULL; SETUP_CONTEXT(); realmdn = ldap_context->lrparams->realmdn; if (realmdn == NULL) return EINVAL; /* policyn_dn should be "cn=<policyname>,<realmdn>". */ len1 = strlen(realmdn); len2 = strlen(policy_dn); if (len1 == 0 || len2 == 0 || len1 + 1 >= len2) return EINVAL; plen = len2 - len1 - 1; if (policy_dn[plen] != ',' || strcmp(realmdn, policy_dn + plen + 1) != 0) return EINVAL; #if defined HAVE_LDAP_STR2DN { char *rdn; LDAPDN dn; rdn = k5memdup0(policy_dn, plen, &ret); if (rdn == NULL) return ret; ret = ldap_str2dn(rdn, &dn, LDAP_DN_FORMAT_LDAPV3 | LDAP_DN_PEDANTIC); free(rdn); if (ret) return EINVAL; if (dn[0] == NULL || dn[1] != NULL || dn[0][0]->la_attr.bv_len != 2 || strncasecmp(dn[0][0]->la_attr.bv_val, "cn", 2) != 0) { ret = EINVAL; } else { *name_out = k5memdup0(dn[0][0]->la_value.bv_val, dn[0][0]->la_value.bv_len, &ret); } ldap_dnfree(dn); return ret; } #elif defined HAVE_LDAP_EXPLODE_DN { char **parsed_dn; /* 1 = return DN components without type prefix */ parsed_dn = ldap_explode_dn(policy_dn, 1); if (parsed_dn == NULL) return EINVAL; *name_out = strdup(parsed_dn[0]); if (*name_out == NULL) return ENOMEM; ldap_value_free(parsed_dn); return 0; } #else return EINVAL; #endif }
/* Used by the slave to update its hash db from* the incr update log. Must be * called with lock held. */ krb5_error_code ulog_replay(krb5_context context, kdb_incr_result_t *incr_ret, char **db_args) { krb5_db_entry *entry = NULL; kdb_incr_update_t *upd = NULL, *fupd; int i, no_of_updates; krb5_error_code retval; krb5_principal dbprinc; kdb_last_t errlast, *last; char *dbprincstr; kdb_log_context *log_ctx; kdb_hlog_t *ulog = NULL; INIT_ULOG(context); no_of_updates = incr_ret->updates.kdb_ulog_t_len; upd = incr_ret->updates.kdb_ulog_t_val; fupd = upd; /* We reset last_sno and last_time to 0, if krb5_db2_db_put_principal or * krb5_db2_db_delete_principal fail. */ errlast.last_sno = (unsigned int)0; errlast.last_time.seconds = (unsigned int)0; errlast.last_time.useconds = (unsigned int)0; last = &errlast; retval = krb5_db_open(context, db_args, KRB5_KDB_OPEN_RW | KRB5_KDB_SRV_TYPE_ADMIN); if (retval) goto cleanup; for (i = 0; i < no_of_updates; i++) { if (!upd->kdb_commit) continue; if (upd->kdb_deleted) { dbprincstr = k5memdup0(upd->kdb_princ_name.utf8str_t_val, upd->kdb_princ_name.utf8str_t_len, &retval); if (dbprincstr == NULL) goto cleanup; retval = krb5_parse_name(context, dbprincstr, &dbprinc); free(dbprincstr); if (retval) goto cleanup; retval = krb5int_delete_principal_no_log(context, dbprinc); krb5_free_principal(context, dbprinc); if (retval) goto cleanup; } else { entry = k5alloc(sizeof(krb5_db_entry), &retval); if (entry == NULL) goto cleanup; retval = ulog_conv_2dbentry(context, &entry, upd); if (retval) goto cleanup; retval = krb5int_put_principal_no_log(context, entry); krb5_db_free_principal(context, entry); if (retval) goto cleanup; } upd++; } last = &incr_ret->lastentry; cleanup: if (fupd) ulog_free_entries(fupd, no_of_updates); /* Record a new last serial number and timestamp in the ulog header. */ ulog->kdb_last_sno = last->last_sno; ulog->kdb_last_time = last->last_time; ulog_sync_header(ulog); return retval; }
static krb5_error_code module_locate_server(krb5_context ctx, const krb5_data *realm, struct serverlist *serverlist, enum locate_service_type svc, int socktype) { struct krb5plugin_service_locate_result *res = NULL; krb5_error_code code; struct krb5plugin_service_locate_ftable *vtbl = NULL; void **ptrs; char *realmz; /* NUL-terminated realm */ int i; struct module_callback_data cbdata = { 0, }; const char *msg; Tprintf("in module_locate_server\n"); cbdata.list = serverlist; if (!PLUGIN_DIR_OPEN (&ctx->libkrb5_plugins)) { code = krb5int_open_plugin_dirs (objdirs, NULL, &ctx->libkrb5_plugins, &ctx->err); if (code) return KRB5_PLUGIN_NO_HANDLE; } code = krb5int_get_plugin_dir_data (&ctx->libkrb5_plugins, "service_locator", &ptrs, &ctx->err); if (code) { Tprintf("error looking up plugin symbols: %s\n", (msg = krb5_get_error_message(ctx, code))); krb5_free_error_message(ctx, msg); return KRB5_PLUGIN_NO_HANDLE; } if (realm->length >= UINT_MAX) { krb5int_free_plugin_dir_data(ptrs); return ENOMEM; } realmz = k5memdup0(realm->data, realm->length, &code); if (realmz == NULL) { krb5int_free_plugin_dir_data(ptrs); return code; } for (i = 0; ptrs[i]; i++) { void *blob; vtbl = ptrs[i]; Tprintf("element %d is %p\n", i, ptrs[i]); /* For now, don't keep the plugin data alive. For long-lived contexts, it may be desirable to change that later. */ code = vtbl->init(ctx, &blob); if (code) continue; code = vtbl->lookup(blob, svc, realmz, (socktype != 0) ? socktype : SOCK_DGRAM, AF_UNSPEC, module_callback, &cbdata); /* Also ask for TCP addresses if we got UDP addresses and want both. */ if (code == 0 && socktype == 0) { code = vtbl->lookup(blob, svc, realmz, SOCK_STREAM, AF_UNSPEC, module_callback, &cbdata); if (code == KRB5_PLUGIN_NO_HANDLE) code = 0; } vtbl->fini(blob); if (code == KRB5_PLUGIN_NO_HANDLE) { /* Module passes, keep going. */ /* XXX */ Tprintf("plugin doesn't handle this realm (KRB5_PLUGIN_NO_HANDLE)\n"); continue; } if (code != 0) { /* Module encountered an actual error. */ Tprintf("plugin lookup routine returned error %d: %s\n", code, error_message(code)); free(realmz); krb5int_free_plugin_dir_data (ptrs); return code; } break; } if (ptrs[i] == NULL) { Tprintf("ran off end of plugin list\n"); free(realmz); krb5int_free_plugin_dir_data (ptrs); return KRB5_PLUGIN_NO_HANDLE; } Tprintf("stopped with plugin #%d, res=%p\n", i, res); /* Got something back, yippee. */ Tprintf("now have %lu addrs in list %p\n", (unsigned long) serverlist->nservers, serverlist); free(realmz); krb5int_free_plugin_dir_data (ptrs); return 0; }
static krb5_error_code locate_srv_conf_1(krb5_context context, const krb5_data *realm, const char * name, struct serverlist *serverlist, k5_transport transport, int udpport) { const char *realm_srv_names[4]; char **hostlist = NULL, *realmstr = NULL, *host = NULL; const char *hostspec; krb5_error_code code; int i, default_port; Tprintf("looking in krb5.conf for realm %s entry %s; ports %d,%d\n", realm->data, name, udpport); realmstr = k5memdup0(realm->data, realm->length, &code); if (realmstr == NULL) goto cleanup; realm_srv_names[0] = KRB5_CONF_REALMS; realm_srv_names[1] = realmstr; realm_srv_names[2] = name; realm_srv_names[3] = 0; code = profile_get_values(context->profile, realm_srv_names, &hostlist); if (code) { Tprintf("config file lookup failed: %s\n", error_message(code)); if (code == PROF_NO_SECTION || code == PROF_NO_RELATION) code = 0; goto cleanup; } for (i = 0; hostlist[i]; i++) { int port_num; k5_transport this_transport = transport; const char *uri_path = NULL; hostspec = hostlist[i]; Tprintf("entry %d is '%s'\n", i, hostspec); parse_uri_if_https(hostspec, &this_transport, &hostspec, &uri_path); default_port = (this_transport == HTTPS) ? 443 : udpport; code = k5_parse_host_string(hostspec, default_port, &host, &port_num); if (code == 0 && host == NULL) code = EINVAL; if (code) goto cleanup; code = add_host_to_list(serverlist, host, port_num, this_transport, AF_UNSPEC, uri_path, -1); if (code) goto cleanup; free(host); host = NULL; } cleanup: free(realmstr); free(host); profile_free_list(hostlist); return code; }
/* Used by the slave to update its hash db from the incr update log. */ krb5_error_code ulog_replay(krb5_context context, kdb_incr_result_t *incr_ret, char **db_args) { krb5_db_entry *entry = NULL; kdb_incr_update_t *upd = NULL, *fupd; int i, no_of_updates; krb5_error_code retval; krb5_principal dbprinc; char *dbprincstr; kdb_log_context *log_ctx; kdb_hlog_t *ulog = NULL; INIT_ULOG(context); /* Lock the DB before the ulog to avoid deadlock. */ retval = krb5_db_open(context, db_args, KRB5_KDB_OPEN_RW | KRB5_KDB_SRV_TYPE_ADMIN); if (retval) return retval; retval = krb5_db_lock(context, KRB5_DB_LOCKMODE_EXCLUSIVE); if (retval) return retval; retval = lock_ulog(context, KRB5_LOCKMODE_EXCLUSIVE); if (retval) { krb5_db_unlock(context); return retval; } no_of_updates = incr_ret->updates.kdb_ulog_t_len; upd = incr_ret->updates.kdb_ulog_t_val; fupd = upd; for (i = 0; i < no_of_updates; i++) { if (!upd->kdb_commit) continue; /* If (unexpectedly) this update does not follow the last one we * stored, discard any previous ulog state. */ if (ulog->kdb_num != 0 && upd->kdb_entry_sno != ulog->kdb_last_sno + 1) reset_header(ulog); if (upd->kdb_deleted) { dbprincstr = k5memdup0(upd->kdb_princ_name.utf8str_t_val, upd->kdb_princ_name.utf8str_t_len, &retval); if (dbprincstr == NULL) goto cleanup; retval = krb5_parse_name(context, dbprincstr, &dbprinc); free(dbprincstr); if (retval) goto cleanup; retval = krb5int_delete_principal_no_log(context, dbprinc); krb5_free_principal(context, dbprinc); if (retval == KRB5_KDB_NOENTRY) retval = 0; if (retval) goto cleanup; } else { entry = k5alloc(sizeof(krb5_db_entry), &retval); if (entry == NULL) goto cleanup; retval = ulog_conv_2dbentry(context, &entry, upd); if (retval) goto cleanup; retval = krb5int_put_principal_no_log(context, entry); krb5_db_free_principal(context, entry); if (retval) goto cleanup; } retval = store_update(log_ctx, upd); if (retval) goto cleanup; upd++; } cleanup: if (fupd) ulog_free_entries(fupd, no_of_updates); if (retval) { reset_header(ulog); sync_header(ulog); } unlock_ulog(context); krb5_db_unlock(context); return retval; }
static krb5_error_code process_chpw_request(krb5_context context, void *server_handle, char *realm, krb5_keytab keytab, const krb5_fulladdr *local_faddr, const krb5_fulladdr *remote_faddr, krb5_data *req, krb5_data *rep) { krb5_error_code ret; char *ptr; unsigned int plen, vno; krb5_data ap_req, ap_rep = empty_data(); krb5_data cipher = empty_data(), clear = empty_data(); krb5_auth_context auth_context = NULL; krb5_principal changepw = NULL; krb5_principal client, target = NULL; krb5_ticket *ticket = NULL; krb5_replay_data replay; krb5_error krberror; int numresult; char strresult[1024]; char *clientstr = NULL, *targetstr = NULL; const char *errmsg = NULL; size_t clen; char *cdots; struct sockaddr_storage ss; socklen_t salen; char addrbuf[100]; krb5_address *addr = remote_faddr->address; *rep = empty_data(); if (req->length < 4) { /* either this, or the server is printing bad messages, or the caller passed in garbage */ ret = KRB5KRB_AP_ERR_MODIFIED; numresult = KRB5_KPASSWD_MALFORMED; strlcpy(strresult, "Request was truncated", sizeof(strresult)); goto bailout; } ptr = req->data; /* verify length */ plen = (*ptr++ & 0xff); plen = (plen<<8) | (*ptr++ & 0xff); if (plen != req->length) { ret = KRB5KRB_AP_ERR_MODIFIED; numresult = KRB5_KPASSWD_MALFORMED; strlcpy(strresult, "Request length was inconsistent", sizeof(strresult)); goto bailout; } /* verify version number */ vno = (*ptr++ & 0xff) ; vno = (vno<<8) | (*ptr++ & 0xff); if (vno != 1 && vno != RFC3244_VERSION) { ret = KRB5KDC_ERR_BAD_PVNO; numresult = KRB5_KPASSWD_BAD_VERSION; snprintf(strresult, sizeof(strresult), "Request contained unknown protocol version number %d", vno); goto bailout; } /* read, check ap-req length */ ap_req.length = (*ptr++ & 0xff); ap_req.length = (ap_req.length<<8) | (*ptr++ & 0xff); if (ptr + ap_req.length >= req->data + req->length) { ret = KRB5KRB_AP_ERR_MODIFIED; numresult = KRB5_KPASSWD_MALFORMED; strlcpy(strresult, "Request was truncated in AP-REQ", sizeof(strresult)); goto bailout; } /* verify ap_req */ ap_req.data = ptr; ptr += ap_req.length; ret = krb5_auth_con_init(context, &auth_context); if (ret) { numresult = KRB5_KPASSWD_HARDERROR; strlcpy(strresult, "Failed initializing auth context", sizeof(strresult)); goto chpwfail; } ret = krb5_auth_con_setflags(context, auth_context, KRB5_AUTH_CONTEXT_DO_SEQUENCE); if (ret) { numresult = KRB5_KPASSWD_HARDERROR; strlcpy(strresult, "Failed initializing auth context", sizeof(strresult)); goto chpwfail; } ret = krb5_build_principal(context, &changepw, strlen(realm), realm, "kadmin", "changepw", NULL); if (ret) { numresult = KRB5_KPASSWD_HARDERROR; strlcpy(strresult, "Failed building kadmin/changepw principal", sizeof(strresult)); goto chpwfail; } ret = krb5_rd_req(context, &auth_context, &ap_req, changepw, keytab, NULL, &ticket); if (ret) { numresult = KRB5_KPASSWD_AUTHERROR; strlcpy(strresult, "Failed reading application request", sizeof(strresult)); goto chpwfail; } /* construct the ap-rep */ ret = krb5_mk_rep(context, auth_context, &ap_rep); if (ret) { numresult = KRB5_KPASSWD_AUTHERROR; strlcpy(strresult, "Failed replying to application request", sizeof(strresult)); goto chpwfail; } /* decrypt the ChangePasswdData */ cipher.length = (req->data + req->length) - ptr; cipher.data = ptr; /* * Don't set a remote address in auth_context before calling krb5_rd_priv, * so that we can work against clients behind a NAT. Reflection attacks * aren't a concern since we use sequence numbers and since our requests * don't look anything like our responses. Also don't set a local address, * since we don't know what interface the request was received on. */ ret = krb5_rd_priv(context, auth_context, &cipher, &clear, &replay); if (ret) { numresult = KRB5_KPASSWD_HARDERROR; strlcpy(strresult, "Failed decrypting request", sizeof(strresult)); goto chpwfail; } client = ticket->enc_part2->client; /* decode ChangePasswdData for setpw requests */ if (vno == RFC3244_VERSION) { krb5_data *clear_data; ret = decode_krb5_setpw_req(&clear, &clear_data, &target); if (ret != 0) { numresult = KRB5_KPASSWD_MALFORMED; strlcpy(strresult, "Failed decoding ChangePasswdData", sizeof(strresult)); goto chpwfail; } zapfree(clear.data, clear.length); clear = *clear_data; free(clear_data); if (target != NULL) { ret = krb5_unparse_name(context, target, &targetstr); if (ret != 0) { numresult = KRB5_KPASSWD_HARDERROR; strlcpy(strresult, "Failed unparsing target name for log", sizeof(strresult)); goto chpwfail; } } } ret = krb5_unparse_name(context, client, &clientstr); if (ret) { numresult = KRB5_KPASSWD_HARDERROR; strlcpy(strresult, "Failed unparsing client name for log", sizeof(strresult)); goto chpwfail; } /* for cpw, verify that this is an AS_REQ ticket */ if (vno == 1 && (ticket->enc_part2->flags & TKT_FLG_INITIAL) == 0) { numresult = KRB5_KPASSWD_INITIAL_FLAG_NEEDED; strlcpy(strresult, "Ticket must be derived from a password", sizeof(strresult)); goto chpwfail; } /* change the password */ ptr = k5memdup0(clear.data, clear.length, &ret); ret = schpw_util_wrapper(server_handle, client, target, (ticket->enc_part2->flags & TKT_FLG_INITIAL) != 0, ptr, NULL, strresult, sizeof(strresult)); if (ret) errmsg = krb5_get_error_message(context, ret); /* zap the password */ zapfree(clear.data, clear.length); zapfree(ptr, clear.length); clear = empty_data(); clen = strlen(clientstr); trunc_name(&clen, &cdots); switch (addr->addrtype) { case ADDRTYPE_INET: { struct sockaddr_in *sin = ss2sin(&ss); sin->sin_family = AF_INET; memcpy(&sin->sin_addr, addr->contents, addr->length); sin->sin_port = htons(remote_faddr->port); salen = sizeof(*sin); break; } case ADDRTYPE_INET6: { struct sockaddr_in6 *sin6 = ss2sin6(&ss); sin6->sin6_family = AF_INET6; memcpy(&sin6->sin6_addr, addr->contents, addr->length); sin6->sin6_port = htons(remote_faddr->port); salen = sizeof(*sin6); break; } default: { struct sockaddr *sa = ss2sa(&ss); sa->sa_family = AF_UNSPEC; salen = sizeof(*sa); break; } } if (getnameinfo(ss2sa(&ss), salen, addrbuf, sizeof(addrbuf), NULL, 0, NI_NUMERICHOST | NI_NUMERICSERV) != 0) strlcpy(addrbuf, "<unprintable>", sizeof(addrbuf)); if (vno == RFC3244_VERSION) { size_t tlen; char *tdots; const char *targetp; if (target == NULL) { tlen = clen; tdots = cdots; targetp = targetstr; } else { tlen = strlen(targetstr); trunc_name(&tlen, &tdots); targetp = clientstr; } krb5_klog_syslog(LOG_NOTICE, _("setpw request from %s by %.*s%s for " "%.*s%s: %s"), addrbuf, (int) clen, clientstr, cdots, (int) tlen, targetp, tdots, errmsg ? errmsg : "success"); } else { krb5_klog_syslog(LOG_NOTICE, _("chpw request from %s for %.*s%s: %s"), addrbuf, (int) clen, clientstr, cdots, errmsg ? errmsg : "success"); } switch (ret) { case KADM5_AUTH_CHANGEPW: numresult = KRB5_KPASSWD_ACCESSDENIED; break; case KADM5_PASS_Q_TOOSHORT: case KADM5_PASS_REUSE: case KADM5_PASS_Q_CLASS: case KADM5_PASS_Q_DICT: case KADM5_PASS_Q_GENERIC: case KADM5_PASS_TOOSOON: numresult = KRB5_KPASSWD_SOFTERROR; break; case 0: numresult = KRB5_KPASSWD_SUCCESS; strlcpy(strresult, "", sizeof(strresult)); break; default: numresult = KRB5_KPASSWD_HARDERROR; break; } chpwfail: ret = alloc_data(&clear, 2 + strlen(strresult)); if (ret) goto bailout; ptr = clear.data; *ptr++ = (numresult>>8) & 0xff; *ptr++ = numresult & 0xff; memcpy(ptr, strresult, strlen(strresult)); cipher = empty_data(); if (ap_rep.length) { ret = krb5_auth_con_setaddrs(context, auth_context, local_faddr->address, NULL); if (ret) { numresult = KRB5_KPASSWD_HARDERROR; strlcpy(strresult, "Failed storing client and server internet addresses", sizeof(strresult)); } else { ret = krb5_mk_priv(context, auth_context, &clear, &cipher, &replay); if (ret) { numresult = KRB5_KPASSWD_HARDERROR; strlcpy(strresult, "Failed encrypting reply", sizeof(strresult)); } } } /* if no KRB-PRIV was constructed, then we need a KRB-ERROR. if this fails, just bail. there's nothing else we can do. */ if (cipher.length == 0) { /* clear out ap_rep now, so that it won't be inserted in the reply */ if (ap_rep.length) { free(ap_rep.data); ap_rep = empty_data(); } krberror.ctime = 0; krberror.cusec = 0; krberror.susec = 0; ret = krb5_timeofday(context, &krberror.stime); if (ret) goto bailout; /* this is really icky. but it's what all the other callers to mk_error do. */ krberror.error = ret; krberror.error -= ERROR_TABLE_BASE_krb5; if (krberror.error < 0 || krberror.error > KRB_ERR_MAX) krberror.error = KRB_ERR_GENERIC; krberror.client = NULL; ret = krb5_build_principal(context, &krberror.server, strlen(realm), realm, "kadmin", "changepw", NULL); if (ret) goto bailout; krberror.text.length = 0; krberror.e_data = clear; ret = krb5_mk_error(context, &krberror, &cipher); krb5_free_principal(context, krberror.server); if (ret) goto bailout; } /* construct the reply */ ret = alloc_data(rep, 6 + ap_rep.length + cipher.length); if (ret) goto bailout; ptr = rep->data; /* length */ *ptr++ = (rep->length>>8) & 0xff; *ptr++ = rep->length & 0xff; /* version == 0x0001 big-endian */ *ptr++ = 0; *ptr++ = 1; /* ap_rep length, big-endian */ *ptr++ = (ap_rep.length>>8) & 0xff; *ptr++ = ap_rep.length & 0xff; /* ap-rep data */ if (ap_rep.length) { memcpy(ptr, ap_rep.data, ap_rep.length); ptr += ap_rep.length; } /* krb-priv or krb-error */ memcpy(ptr, cipher.data, cipher.length); bailout: krb5_auth_con_free(context, auth_context); krb5_free_principal(context, changepw); krb5_free_ticket(context, ticket); free(ap_rep.data); free(clear.data); free(cipher.data); krb5_free_principal(context, target); krb5_free_unparsed_name(context, targetstr); krb5_free_unparsed_name(context, clientstr); krb5_free_error_message(context, errmsg); return ret; }