/* SSSD PAC Responder - utility finctions Copyright (C) Sumit Bose 2012 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 #include #include #include #include "util/util.h" #include "responder/pac/pacsrv.h" /** * Check if a given SID belongs to a domain identified by the domain SID. */ bool dom_sid_in_domain(const struct dom_sid *domain_sid, const struct dom_sid *sid) { size_t c; if (!domain_sid || !sid) { return false; } if (domain_sid->sid_rev_num != sid->sid_rev_num) { return false; } for (c = 0; c < 6; c++) { if (domain_sid->id_auth[c] != sid->id_auth[c]) { return false; } } if (domain_sid->num_auths > sid->num_auths) { return false; } for (c = 0; c < domain_sid->num_auths-1; c++) { if (domain_sid->sub_auths[c] != sid->sub_auths[c]) { return false; } } return true; } errno_t get_sids_from_pac(TALLOC_CTX *mem_ctx, struct pac_ctx *pac_ctx, struct PAC_LOGON_INFO *logon_info, char **_user_sid_str, char **_primary_group_sid_str, hash_table_t **_sid_table) { int ret; size_t s; struct netr_SamInfo3 *info3; struct sss_domain_info *user_dom; struct sss_domain_info *group_dom; char *sid_str = NULL; char *msid_str = NULL; char *user_dom_sid_str = NULL; size_t user_dom_sid_str_len; enum idmap_error_code err; hash_table_t *sid_table = NULL; hash_key_t key; hash_value_t value; char *rid_start; struct ldb_result *msg = NULL; char *user_sid_str = NULL; char *primary_group_sid_str = NULL; if (pac_ctx == NULL || logon_info == NULL || _sid_table == NULL) { DEBUG(SSSDBG_OP_FAILURE, ("Missing parameter.\n")); return EINVAL; } info3 = &logon_info->info3; ret = sss_hash_create(mem_ctx, info3->sidcount + info3->base.groups.count + 2, &sid_table); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, ("sss_hash_create failed.\n")); goto done; } key.type = HASH_KEY_STRING; value.type = HASH_VALUE_ULONG; err = sss_idmap_smb_sid_to_sid(pac_ctx->idmap_ctx, info3->base.domain_sid, &user_dom_sid_str); if (err != IDMAP_SUCCESS) { DEBUG(SSSDBG_OP_FAILURE, ("sss_idmap_smb_sid_to_sid failed.\n")); ret = EFAULT; goto done; } ret = responder_get_domain_by_id(pac_ctx->rctx, user_dom_sid_str, &user_dom); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, ("responder_get_domain_by_id failed.\n")); ret = EINVAL; goto done; } user_dom_sid_str_len = strlen(user_dom_sid_str); sid_str = talloc_zero_size(mem_ctx, user_dom_sid_str_len + 12); if (sid_str == NULL) { DEBUG(SSSDBG_OP_FAILURE, ("talloc_zero_size failed.\n")); ret = ENOMEM; goto done; } rid_start = sid_str + user_dom_sid_str_len; memcpy(sid_str, user_dom_sid_str, user_dom_sid_str_len); memset(rid_start, '\0', 12); ret = snprintf(rid_start, 12, "-%lu", (unsigned long) info3->base.rid); if (ret < 0 || ret > 12) { DEBUG(SSSDBG_OP_FAILURE, ("snprintf failed.\n")); ret = EIO; goto done; } user_sid_str = talloc_strdup(mem_ctx, sid_str); if (user_sid_str == NULL) { DEBUG(SSSDBG_OP_FAILURE, ("talloc_strdup failed.\n")); ret = ENOMEM; goto done; } key.str = sid_str; value.ul = 0; ret = sysdb_search_object_by_sid(mem_ctx, user_dom->sysdb, user_dom, sid_str, NULL, &msg); if (ret == EOK && msg->count == 1) { value.ul = ldb_msg_find_attr_as_uint64(msg->msgs[0], SYSDB_UIDNUM, 0); } talloc_zfree(msg); ret = hash_enter(sid_table, &key, &value); if (ret != HASH_SUCCESS) { DEBUG(SSSDBG_OP_FAILURE, ("hash_enter failed [%d][%s].\n", ret, hash_error_string(ret))); ret = EIO; goto done; } memset(rid_start, '\0', 12); ret = snprintf(rid_start, 12, "-%lu", (unsigned long) info3->base.primary_gid); if (ret < 0 || ret > 12) { DEBUG(SSSDBG_OP_FAILURE, ("snprintf failed.\n")); ret = EIO; goto done; } primary_group_sid_str = talloc_strdup(mem_ctx, sid_str); if (primary_group_sid_str == NULL) { DEBUG(SSSDBG_OP_FAILURE, ("talloc_strdup failed.\n")); ret = ENOMEM; goto done; } key.str = sid_str; value.ul = 0; ret = sysdb_search_object_by_sid(mem_ctx, user_dom->sysdb, user_dom, sid_str, NULL, &msg); if (ret == EOK && msg->count == 1) { value.ul = ldb_msg_find_attr_as_uint64(msg->msgs[0], SYSDB_GIDNUM, 0); } talloc_zfree(msg); ret = hash_enter(sid_table, &key, &value); if (ret != HASH_SUCCESS) { DEBUG(SSSDBG_OP_FAILURE, ("hash_enter failed [%d][%s].\n", ret, hash_error_string(ret))); ret = EIO; goto done; } for (s = 0; s < info3->base.groups.count; s++) { memset(rid_start, '\0', 12); ret = snprintf(rid_start, 12, "-%lu", (unsigned long) info3->base.groups.rids[s].rid); if (ret < 0 || ret > 12) { DEBUG(SSSDBG_OP_FAILURE, ("snprintf failed.\n")); ret = EIO; goto done; } key.str = sid_str; value.ul = 0; ret = sysdb_search_object_by_sid(mem_ctx, user_dom->sysdb, user_dom, sid_str, NULL, &msg); if (ret == EOK && msg->count == 1) { value.ul = ldb_msg_find_attr_as_uint64(msg->msgs[0], SYSDB_GIDNUM, 0); } talloc_zfree(msg); ret = hash_enter(sid_table, &key, &value); if (ret != HASH_SUCCESS) { DEBUG(SSSDBG_OP_FAILURE, ("hash_enter failed [%d][%s].\n", ret, hash_error_string(ret))); ret = EIO; goto done; } } for(s = 0; s < info3->sidcount; s++) { err = sss_idmap_smb_sid_to_sid(pac_ctx->idmap_ctx, info3->sids[s].sid, &msid_str); if (err != IDMAP_SUCCESS) { DEBUG(SSSDBG_OP_FAILURE, ("sss_idmap_smb_sid_to_sid failed.\n")); ret = EFAULT; goto done; } key.str = msid_str; value.ul = 0; ret = responder_get_domain_by_id(pac_ctx->rctx, msid_str, &group_dom); if (ret == EOK) { ret = sysdb_search_object_by_sid(mem_ctx, group_dom->sysdb, group_dom, msid_str, NULL, &msg); if (ret == EOK && msg->count == 1 ) { value.ul = ldb_msg_find_attr_as_uint64(msg->msgs[0], SYSDB_GIDNUM, 0); } talloc_zfree(msg); } ret = hash_enter(sid_table, &key, &value); sss_idmap_free_sid(pac_ctx->idmap_ctx, msid_str); if (ret != HASH_SUCCESS) { DEBUG(SSSDBG_OP_FAILURE, ("hash_enter failed [%d][%s].\n", ret, hash_error_string(ret))); ret = EIO; goto done; } } ret = EOK; done: talloc_free(sid_str); sss_idmap_free_sid(pac_ctx->idmap_ctx, user_dom_sid_str); if (ret == EOK) { *_sid_table = sid_table; *_user_sid_str = user_sid_str; *_primary_group_sid_str = primary_group_sid_str; } else { hash_destroy(sid_table); talloc_free(user_sid_str); talloc_free(primary_group_sid_str); } return ret; } /** * Extract the PAC logon data from an NDR blob. */ errno_t get_data_from_pac(TALLOC_CTX *mem_ctx, uint8_t *pac_blob, size_t pac_len, struct PAC_LOGON_INFO **_logon_info) { DATA_BLOB blob; struct ndr_pull *ndr_pull; struct PAC_DATA *pac_data; enum ndr_err_code ndr_err; size_t c; int ret; blob.data = pac_blob; blob.length = pac_len; ndr_pull = ndr_pull_init_blob(&blob, mem_ctx); if (ndr_pull == NULL) { DEBUG(SSSDBG_OP_FAILURE, ("ndr_pull_init_blob failed.\n")); return ENOMEM; } ndr_pull->flags |= LIBNDR_FLAG_REF_ALLOC; /* FIXME: is this really needed ? */ pac_data = talloc_zero(mem_ctx, struct PAC_DATA); if (pac_data == NULL) { DEBUG(SSSDBG_OP_FAILURE, ("talloc_zero failed.\n")); return ENOMEM; } ndr_err = ndr_pull_PAC_DATA(ndr_pull, NDR_SCALARS|NDR_BUFFERS, pac_data); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { DEBUG(SSSDBG_OP_FAILURE, ("ndr_pull_PAC_DATA failed [%d]\n", ndr_err)); return EBADMSG; } for(c = 0; c < pac_data->num_buffers; c++) { if (pac_data->buffers[c].type == PAC_TYPE_LOGON_INFO) { *_logon_info = pac_data->buffers[c].info->logon_info.info; return EOK; } } ret = EINVAL; talloc_free(pac_data); return ret; } /** * Fill up the passwd struct with data from the PAC logon info */ errno_t get_pwd_from_pac(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom, char *user_sid_str, char *primary_group_sid_str, hash_table_t *sid_table, struct PAC_LOGON_INFO *logon_info, struct passwd **_pwd, struct sysdb_attrs **_attrs) { struct passwd *pwd = NULL; struct sysdb_attrs *attrs = NULL; struct netr_SamBaseInfo *base_info; int ret; char *lname; char *uc_realm; char *upn; hash_key_t key; hash_value_t value; pwd = talloc_zero(mem_ctx, struct passwd); if (pwd == NULL) { DEBUG(SSSDBG_OP_FAILURE, ("talloc_zero failed.\n")); return ENOMEM; } base_info = &logon_info->info3.base; if (base_info->account_name.size == 0) { DEBUG(SSSDBG_OP_FAILURE, ("Missing account name in PAC.\n")); ret = EINVAL; goto done; } if (base_info->rid == 0) { DEBUG(SSSDBG_OP_FAILURE, ("Missing user RID in PAC.\n")); ret = EINVAL; goto done; } /* To be compatible with winbind based lookups we have to use lower * case names only, effectively making the domain case-insenvitive. */ lname = sss_tc_utf8_str_tolower(pwd, base_info->account_name.string); if (lname == NULL) { DEBUG(SSSDBG_OP_FAILURE, ("sss_tc_utf8_str_tolower failed.\n")); ret = ENOMEM; goto done; } /* Subdomain use fully qualified names */ pwd->pw_name = sss_get_domain_name(pwd, lname, dom); if (!pwd->pw_name) { DEBUG(SSSDBG_OP_FAILURE, ("talloc_sprintf failed.\n")); ret = ENOMEM; goto done; } key.type = HASH_KEY_STRING; key.str = user_sid_str; ret = hash_lookup(sid_table, &key, &value); if (ret != HASH_SUCCESS) { DEBUG(SSSDBG_OP_FAILURE, ("hash_lookup failed.\n")); ret = EIO; goto done; } if (value.type != HASH_VALUE_ULONG) { DEBUG(SSSDBG_OP_FAILURE, ("Wrong value type.\n")); ret = EIO; goto done; } pwd->pw_uid = value.ul; if (IS_SUBDOMAIN(dom) || dom->mpg) { pwd->pw_gid = 0; /* We use MPGs for sub-domains */ } else { key.type = HASH_KEY_STRING; key.str = primary_group_sid_str; ret = hash_lookup(sid_table, &key, &value); if (ret != HASH_SUCCESS) { DEBUG(SSSDBG_OP_FAILURE, ("hash_lookup failed.\n")); ret = EIO; goto done; } if (value.type != HASH_VALUE_ULONG) { DEBUG(SSSDBG_OP_FAILURE, ("Wrong value type.\n")); ret = EIO; goto done; } pwd->pw_gid = value.ul; } if (base_info->full_name.size != 0) { pwd->pw_gecos = talloc_strdup(pwd, base_info->full_name.string); if (pwd->pw_gecos == NULL) { DEBUG(SSSDBG_OP_FAILURE, ("talloc_strdup failed.\n")); ret = ENOMEM; goto done; } } else { DEBUG(SSSDBG_OP_FAILURE, ("Missing full name in PAC, " "gecos field will by empty.\n ")); } /* Check if there is a special homedir template for sub-domains. If not a * fallback will be added by the NSS responder. */ if (IS_SUBDOMAIN(dom) && dom->subdomain_homedir) { pwd->pw_dir = expand_homedir_template(pwd, dom->subdomain_homedir, lname, pwd->pw_uid, NULL, dom->name, dom->flat_name); if (pwd->pw_dir == NULL) { ret = ENOMEM; goto done; } } pwd->pw_shell = NULL; /* Using default */ attrs = sysdb_new_attrs(mem_ctx); if (attrs == NULL) { DEBUG(SSSDBG_OP_FAILURE, ("sysdb_new_attrs failed.\n")); ret = ENOMEM; goto done; } uc_realm = get_uppercase_realm(mem_ctx, dom->name); if (uc_realm == NULL) { DEBUG(SSSDBG_OP_FAILURE, ("get_uppercase_realm failed.\n")); ret = ENOMEM; goto done; } upn = talloc_asprintf(mem_ctx, "%s@%s", lname, uc_realm); talloc_free(uc_realm); if (upn == NULL) { DEBUG(SSSDBG_OP_FAILURE, ("talloc_asprintf failed.\n")); ret = ENOMEM; goto done; } ret = sysdb_attrs_add_string(attrs, SYSDB_UPN, upn); talloc_free(upn); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, ("sysdb_attrs_add_string failed.\n")); goto done; } ret = sysdb_attrs_add_lc_name_alias(attrs, pwd->pw_name); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, ("sysdb_attrs_add_lc_name_alias failed.\n")); goto done; } ret = sysdb_attrs_add_string(attrs, SYSDB_SID_STR, user_sid_str); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, ("sysdb_attrs_add_string failed.\n")); goto done; } *_pwd = pwd; *_attrs = attrs; ret = EOK; done: if (ret != EOK) { talloc_free(pwd); } return ret; }