/* Authors: Pavel Březina Copyright (C) 2016 Red Hat This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "util/crypto/sss_crypto.h" #include "responder/nss/nss_protocol.h" static errno_t find_sss_id_type(struct ldb_message *msg, bool mpg, enum sss_id_type *id_type) { size_t c; struct ldb_message_element *el; struct ldb_val *val = NULL; el = ldb_msg_find_element(msg, SYSDB_OBJECTCATEGORY); if (el == NULL) { DEBUG(SSSDBG_OP_FAILURE, "Objectcategory attribute not found.\n"); return EINVAL; } for (c = 0; c < el->num_values; c++) { val = &(el->values[c]); if (strncasecmp(SYSDB_USER_CLASS, (char *)val->data, val->length) == 0) { break; } } if (c == el->num_values) { *id_type = SSS_ID_TYPE_GID; } else { if (mpg) { *id_type = SSS_ID_TYPE_BOTH; } else { *id_type = SSS_ID_TYPE_UID; } } return EOK; } static errno_t sss_nss_get_id_type(struct sss_nss_cmd_ctx *cmd_ctx, struct cache_req_result *result, enum sss_id_type *_type) { errno_t ret; bool mpg; /* Well known objects are always groups. */ if (result->well_known_object) { *_type = SSS_ID_TYPE_GID; return EOK; } mpg = sss_domain_is_mpg(result->domain) || sss_domain_is_hybrid(result->domain); ret = find_sss_id_type(result->msgs[0], mpg, _type); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Unable to find ID type [%d]: %s\n", ret, sss_strerror(ret)); return ret; } return EOK; } static errno_t sss_nss_get_sid_id_type(struct sss_nss_cmd_ctx *cmd_ctx, struct cache_req_result *result, const char **_sid, uint64_t *_id, enum sss_id_type *_type) { errno_t ret; size_t c; const char *tmp; const char *user_sid = NULL; const char *group_sid = NULL; uint64_t user_uid = 0; uint64_t user_gid = 0; uint64_t group_gid = 0; enum sss_id_type ltype; if (result->count == 1) { *_sid = ldb_msg_find_attr_as_string(result->msgs[0], SYSDB_SID_STR, NULL); if (*_sid == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Missing SID.\n"); return EINVAL; } ret = sss_nss_get_id_type(cmd_ctx, result, _type); if (ret == EOK ) { if (*_type == SSS_ID_TYPE_GID) { *_id = ldb_msg_find_attr_as_uint64(result->msgs[0], SYSDB_GIDNUM, 0); } else { *_id = ldb_msg_find_attr_as_uint64(result->msgs[0], SYSDB_UIDNUM, 0); } } return ret; } for (c = 0; c < result->count; c++) { ret = find_sss_id_type(result->msgs[c], false /* we are only interested in the type * of the object, so mpg setting can * be ignored */, <ype); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Unable to find ID type, ignored [%d][%s].\n", ret, sss_strerror(ret)); continue; } tmp = ldb_msg_find_attr_as_string(result->msgs[c], SYSDB_SID_STR, NULL); if (tmp == NULL) { continue; } if (ltype == SSS_ID_TYPE_GID) { if (tmp != NULL && group_sid != NULL) { DEBUG(SSSDBG_OP_FAILURE, "Search for SID found multiple groups with SIDs: %s, %s;" " request failed.\n", tmp, group_sid); return EINVAL; } group_sid = tmp; group_gid = ldb_msg_find_attr_as_uint64(result->msgs[c], SYSDB_GIDNUM, 0); } else { if (tmp != NULL && user_sid != NULL) { DEBUG(SSSDBG_OP_FAILURE, "Search for SID found multiple users with SIDs: %s, %s; " "request failed.\n", tmp, user_sid); return EINVAL; } user_sid = tmp; user_uid = ldb_msg_find_attr_as_uint64(result->msgs[c], SYSDB_UIDNUM, 0); user_gid = ldb_msg_find_attr_as_uint64(result->msgs[c], SYSDB_GIDNUM, 0); } } if (user_sid == NULL && group_sid == NULL) { /* No SID in the results */ return ENOENT; } else if (user_sid != NULL && group_sid == NULL) { /* There is only one user with a SID in the results */ *_sid = user_sid; *_id = user_uid; *_type = SSS_ID_TYPE_UID; } else if (user_sid == NULL && group_sid != NULL) { /* There is only one group with a SID in the results */ *_sid = group_sid; *_id = group_gid; *_type = SSS_ID_TYPE_GID; } else if (user_sid != NULL && group_sid != NULL && user_uid != 0 && user_uid == user_gid && user_gid == group_gid) { /* Manually created user-private-group */ *_sid = user_sid; *_id = user_uid; *_type = SSS_ID_TYPE_UID; } else { DEBUG(SSSDBG_OP_FAILURE, "Found user with SID [%s] and group with SID [%s] during a " "single request, cannot handle this case.\n", user_sid, group_sid); /* Unrelated user and group both with SIDs are returned, we cannot * handle this case. */ return EINVAL; } return EOK; } errno_t sss_nss_protocol_fill_sid(struct sss_nss_ctx *nss_ctx, struct sss_nss_cmd_ctx *cmd_ctx, struct sss_packet *packet, struct cache_req_result *result) { struct sized_string sz_sid; enum sss_id_type id_type; const char *sid; uint64_t id; size_t rp = 0; size_t body_len; uint8_t *body; errno_t ret; ret = sss_nss_get_sid_id_type(cmd_ctx, result, &sid, &id, &id_type); if (ret != EOK) { return ret; } to_sized_string(&sz_sid, sid); ret = sss_packet_grow(packet, sz_sid.len + 3 * sizeof(uint32_t)); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sss_packet_grow failed.\n"); return ret; } sss_packet_get_body(packet, &body, &body_len); SAFEALIGN_SET_UINT32(&body[rp], 1, &rp); /* Num results. */ SAFEALIGN_SET_UINT32(&body[rp], 0, &rp); /* Reserved. */ SAFEALIGN_SET_UINT32(&body[rp], id_type, &rp); SAFEALIGN_SET_STRING(&body[rp], sz_sid.str, sz_sid.len, &rp); if (nss_ctx->sid_mc_ctx != NULL) { /* no need to check for SSS_NSS_EX_FLAG_INVALIDATE_CACHE since * SID related requests don't support 'flags' */ if (id == 0 || id >= UINT32_MAX) { DEBUG(SSSDBG_OP_FAILURE, "Invalid POSIX ID %lu\n", id); return EOK; } ret = sss_mmap_cache_sid_store(&nss_ctx->sid_mc_ctx, &sz_sid, (uint32_t)id, id_type, cmd_ctx->type != CACHE_REQ_OBJECT_BY_ID); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to store SID='%s' / ID=%lu in mmap cache [%d]: %s!\n", sz_sid.str, id, ret, sss_strerror(ret)); } } return EOK; } static errno_t process_attr_list(TALLOC_CTX *mem_ctx, struct ldb_message *msg, const char **attr_list, struct sized_string **_keys, struct sized_string **_vals, size_t *array_size, size_t *sum, size_t *found) { size_t c; size_t d; struct sized_string *keys; struct sized_string *vals; struct ldb_val val; struct ldb_message_element *el; bool use_base64; keys = *_keys; vals = *_vals; for (c = 0; attr_list[c] != NULL; c++) { el = ldb_msg_find_element(msg, attr_list[c]); if (el != NULL && el->num_values > 0) { if (el->num_values > 1) { *array_size += el->num_values; keys = talloc_realloc(mem_ctx, keys, struct sized_string, *array_size); vals = talloc_realloc(mem_ctx, vals, struct sized_string, *array_size); if (keys == NULL || vals == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n"); return ENOMEM; } } use_base64 = false; if (strcmp(attr_list[c], SYSDB_USER_CERT) == 0) { use_base64 = true; } for (d = 0; d < el->num_values; d++) { to_sized_string(&keys[*found], attr_list[c]); *sum += keys[*found].len; if (use_base64) { val.data = (uint8_t *)sss_base64_encode(vals, el->values[d].data, el->values[d].length); if (val.data != NULL) { val.length = strlen((char *)val.data); } } else { val = el->values[d]; } if (val.data == NULL || val.data[val.length] != '\0') { DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected attribute value found for [%s].\n", attr_list[c]); return EINVAL; } to_sized_string(&vals[*found], (const char *)val.data); *sum += vals[*found].len; (*found)++; } } } *_keys = keys; *_vals = vals; return EOK; } errno_t sss_nss_protocol_fill_orig(struct sss_nss_ctx *nss_ctx, struct sss_nss_cmd_ctx *cmd_ctx, struct sss_packet *packet, struct cache_req_result *result) { TALLOC_CTX *tmp_ctx; struct ldb_message *msg = result->msgs[0]; const char **full_attrs = NULL; enum sss_id_type id_type; struct sized_string *keys; struct sized_string *vals; size_t full_attrs_count = 0; size_t array_size; size_t sum; size_t found; size_t i; size_t rp = 0; size_t body_len; uint8_t *body; errno_t ret; if (result->count != 1) { DEBUG(SSSDBG_OP_FAILURE, "Unexpected number of results [%u], expected [1].\n", result->count); return EINVAL; } tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { return ENOMEM; } ret = sss_nss_get_id_type(cmd_ctx, result, &id_type); if (ret != EOK) { return ret; } if (nss_ctx->full_attribute_list != NULL) { full_attrs = nss_ctx->full_attribute_list; for (full_attrs_count = 0; full_attrs[full_attrs_count] != NULL; full_attrs_count++); } array_size = full_attrs_count; keys = talloc_array(tmp_ctx, struct sized_string, array_size); vals = talloc_array(tmp_ctx, struct sized_string, array_size); if (keys == NULL || vals == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n"); ret = ENOMEM; goto done; } sum = 0; found = 0; if (full_attrs_count != 0) { ret = process_attr_list(tmp_ctx, msg, full_attrs, &keys, &vals, &array_size, &sum, &found); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "process_attr_list failed.\n"); goto done; } } ret = sss_packet_grow(packet, sum + 3 * sizeof(uint32_t)); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sss_packet_grow failed.\n"); goto done; } sss_packet_get_body(packet, &body, &body_len); SAFEALIGN_SETMEM_UINT32(&body[rp], 1, &rp); /* Num results */ SAFEALIGN_SETMEM_UINT32(&body[rp], 0, &rp); /* reserved */ SAFEALIGN_COPY_UINT32(&body[rp], &id_type, &rp); for (i = 0; i < found; i++) { SAFEALIGN_SET_STRING(&body[rp], keys[i].str, keys[i].len, &rp); SAFEALIGN_SET_STRING(&body[rp], vals[i].str, vals[i].len, &rp); } ret = EOK; done: talloc_free(tmp_ctx); return ret; } static errno_t sss_nss_get_well_known_name(TALLOC_CTX *mem_ctx, struct resp_ctx *rctx, struct cache_req_result *result, struct sized_string **_sz_name) { struct sized_string *sz_name; const char *fq_name = NULL; const char *domname; const char *name; name = result->lookup_name; if (name == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Missing name.\n"); return EINVAL; } sz_name = talloc_zero(mem_ctx, struct sized_string); if (sz_name == NULL) { return ENOMEM; } domname = result->domain != NULL ? result->domain->name : result->well_known_domain; if (domname != NULL) { fq_name = sss_tc_fqname2(sz_name, rctx->global_names, domname, domname, name); if (fq_name == NULL) { DEBUG(SSSDBG_OP_FAILURE, "Conversion to fqname failed.\n"); talloc_free(sz_name); return ENOMEM; } name = fq_name; } to_sized_string(sz_name, name); *_sz_name = sz_name; return EOK; } static errno_t sss_nss_get_ad_name(TALLOC_CTX *mem_ctx, struct resp_ctx *rctx, struct cache_req_result *result, struct sized_string **_sz_name) { struct ldb_message *msg = result->msgs[0]; const char *name; errno_t ret; if (result->well_known_object) { return sss_nss_get_well_known_name(mem_ctx, rctx, result, _sz_name); } name = ldb_msg_find_attr_as_string(msg, ORIGINALAD_PREFIX SYSDB_NAME, NULL); if (name == NULL) { name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); } if (name == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Missing name.\n"); return EINVAL; } ret = sized_output_name(mem_ctx, rctx, name, result->domain, _sz_name); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Unable to create sized name [%d]: %s\n", ret, sss_strerror(ret)); return ret; } return EOK; } errno_t sss_nss_protocol_fill_single_name(struct sss_nss_ctx *nss_ctx, struct sss_nss_cmd_ctx *cmd_ctx, struct sss_packet *packet, struct cache_req_result *result) { if (result->ldb_result->count > 1) { DEBUG(SSSDBG_TRACE_FUNC, "Lookup returned more than one result " "but only one was expected.\n"); return EEXIST; } return sss_nss_protocol_fill_name(nss_ctx, cmd_ctx, packet, result); } errno_t sss_nss_protocol_fill_name(struct sss_nss_ctx *nss_ctx, struct sss_nss_cmd_ctx *cmd_ctx, struct sss_packet *packet, struct cache_req_result *result) { struct sized_string *sz_name; enum sss_id_type id_type; size_t rp = 0; size_t body_len; uint8_t *body; errno_t ret; ret = sss_nss_get_id_type(cmd_ctx, result, &id_type); if (ret != EOK) { return ret; } ret = sss_nss_get_ad_name(cmd_ctx, nss_ctx->rctx, result, &sz_name); if (ret != EOK) { return ret; } ret = sss_packet_grow(packet, sz_name->len + 3 * sizeof(uint32_t)); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sss_packet_grow failed.\n"); talloc_free(sz_name); return ret; } sss_packet_get_body(packet, &body, &body_len); SAFEALIGN_SET_UINT32(&body[rp], 1, &rp); /* Num results. */ SAFEALIGN_SET_UINT32(&body[rp], 0, &rp); /* Reserved. */ SAFEALIGN_SET_UINT32(&body[rp], id_type, &rp); SAFEALIGN_SET_STRING(&body[rp], sz_name->str, sz_name->len, &rp); talloc_free(sz_name); return EOK; } errno_t sss_nss_protocol_fill_id(struct sss_nss_ctx *nss_ctx, struct sss_nss_cmd_ctx *cmd_ctx, struct sss_packet *packet, struct cache_req_result *result) { struct ldb_message *msg = result->msgs[0]; enum sss_id_type id_type; uint64_t id64; uint32_t id; const char *sid = NULL; struct sized_string sid_key; size_t rp = 0; size_t body_len; uint8_t *body; errno_t ret; if (result->ldb_result == NULL) { /* This was a well known SID. This is currently unsupported with id. */ return EINVAL; } ret = sss_nss_get_id_type(cmd_ctx, result, &id_type); if (ret != EOK) { return ret; } if (id_type == SSS_ID_TYPE_GID) { id64 = ldb_msg_find_attr_as_uint64(msg, SYSDB_GIDNUM, 0); } else { id64 = ldb_msg_find_attr_as_uint64(msg, SYSDB_UIDNUM, 0); } if (id64 == 0 || id64 >= UINT32_MAX) { DEBUG(SSSDBG_CRIT_FAILURE, "Invalid POSIX ID.\n"); return EINVAL; } id = (uint32_t)id64; ret = sss_packet_grow(packet, 4 * sizeof(uint32_t)); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sss_packet_grow failed.\n"); return ret; } sss_packet_get_body(packet, &body, &body_len); SAFEALIGN_SET_UINT32(&body[rp], 1, &rp); /* Num results. */ SAFEALIGN_SET_UINT32(&body[rp], 0, &rp); /* Reserved. */ SAFEALIGN_SET_UINT32(&body[rp], id_type, &rp); SAFEALIGN_SET_UINT32(&body[rp], id, &rp); if (nss_ctx->sid_mc_ctx != NULL) { /* no need to check for SSS_NSS_EX_FLAG_INVALIDATE_CACHE since * SID related requests don't support 'flags' */ sid = ldb_msg_find_attr_as_string(msg, SYSDB_SID_STR, NULL); if (!sid) { DEBUG(SSSDBG_OP_FAILURE, "Missing SID?!\n"); return EOK; } to_sized_string(&sid_key, sid); ret = sss_mmap_cache_sid_store(&nss_ctx->sid_mc_ctx, &sid_key, id, id_type, cmd_ctx->type != CACHE_REQ_OBJECT_BY_ID); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to store SID='%s' / ID=%d in mmap cache [%d]: %s!\n", sid, id, ret, sss_strerror(ret)); } } return EOK; } errno_t sss_nss_protocol_fill_name_list_all_domains(struct sss_nss_ctx *nss_ctx, struct sss_nss_cmd_ctx *cmd_ctx, struct sss_packet *packet, struct cache_req_result **results) { enum sss_id_type *id_types; size_t rp = 0; size_t body_len; uint8_t *body; errno_t ret; struct sized_string *sz_names; size_t len; size_t c; const char *tmp_str; size_t d; size_t total = 0; size_t iter = 0; if (results == NULL) { return EINVAL; } for (d = 0; results[d] != NULL; d++) { total += results[d]->count; } sz_names = talloc_array(cmd_ctx, struct sized_string, total); if (sz_names == NULL) { return ENOMEM; } id_types = talloc_array(cmd_ctx, enum sss_id_type, total); if (id_types == NULL) { return ENOMEM; } len = 0; for (d = 0; results[d] != NULL; d++) { for (c = 0; c < results[d]->count; c++) { ret = sss_nss_get_id_type(cmd_ctx, results[d], &(id_types[iter])); if (ret != EOK) { return ret; } tmp_str = sss_get_name_from_msg(results[d]->domain, results[d]->msgs[c]); if (tmp_str == NULL) { return EINVAL; } to_sized_string(&(sz_names[iter]), tmp_str); len += sz_names[iter].len; iter++; } } len += (2 + total) * sizeof(uint32_t); ret = sss_packet_grow(packet, len); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sss_packet_grow failed.\n"); return ret; } sss_packet_get_body(packet, &body, &body_len); SAFEALIGN_SET_UINT32(&body[rp], total, &rp); /* Num results. */ SAFEALIGN_SET_UINT32(&body[rp], 0, &rp); /* Reserved. */ for (c = 0; c < total; c++) { SAFEALIGN_SET_UINT32(&body[rp], id_types[c], &rp); SAFEALIGN_SET_STRING(&body[rp], sz_names[c].str, sz_names[c].len, &rp); } return EOK; }