diff options
Diffstat (limited to 'src/responder')
-rw-r--r-- | src/responder/pac/pacsrv.h | 55 | ||||
-rw-r--r-- | src/responder/pac/pacsrv_utils.c | 494 |
2 files changed, 549 insertions, 0 deletions
diff --git a/src/responder/pac/pacsrv.h b/src/responder/pac/pacsrv.h index 0dfe7f9e..f0ffea29 100644 --- a/src/responder/pac/pacsrv.h +++ b/src/responder/pac/pacsrv.h @@ -21,6 +21,11 @@ #ifndef __PACSRV_H__ #define __PACSRV_H__ +#include <stdbool.h> +#include <util/data_blob.h> +#include <ndr.h> +#include <gen_ndr/krb5pac.h> +#include <gen_ndr/ndr_krb5pac.h> #include <stdint.h> #include <sys/un.h> @@ -33,6 +38,8 @@ #include "responder/common/responder_packet.h" #include "responder/common/responder.h" #include "lib/idmap/sss_idmap.h" +#include "util/sss_nss.h" +#include "db/sysdb.h" #define PAC_SBUS_SERVICE_VERSION 0x0001 #define PAC_SBUS_SERVICE_NAME "pac" @@ -40,13 +47,61 @@ #define PAC_PACKET_MAX_RECV_SIZE 1024 struct getent_ctx; +struct dom_sid; struct pac_ctx { struct resp_ctx *rctx; + struct sss_idmap_ctx *idmap_ctx; + struct dom_sid *my_dom_sid; + struct local_mapping_ranges *range_map; +}; + +struct range { + uint32_t min; + uint32_t max; +}; + +struct local_mapping_ranges { + struct range local_ids; + struct range primary_rids; + struct range secondary_rids; }; int pac_cmd_execute(struct cli_ctx *cctx); struct sss_cmd_table *get_pac_cmds(void); +errno_t local_sid_to_id(struct local_mapping_ranges *map, struct dom_sid *sid, + uint32_t *id); + +errno_t add_idmap_domain(struct sss_idmap_ctx *idmap_ctx, + struct sysdb_ctx *sysdb, + const char *domain_name, + const char *dom_sid_str); + +errno_t domsid_rid_to_uid(struct pac_ctx *pac_ctx, + struct sysdb_ctx *sysdb, + const char *domain_name, + struct dom_sid2 *domsid, uint32_t rid, + uid_t *uid); + +errno_t get_my_domain_sid(struct pac_ctx *pac_ctx, + struct sss_domain_info *dom, + struct dom_sid **_sid); + +errno_t get_gids_from_pac(TALLOC_CTX *mem_ctx, + struct local_mapping_ranges *range_map, + struct dom_sid *domain_sid, + struct PAC_LOGON_INFO *logon_info, + size_t *_gid_count, gid_t **_gids); + +errno_t get_data_from_pac(TALLOC_CTX *mem_ctx, + uint8_t *pac_blob, size_t pac_len, + struct PAC_LOGON_INFO **_logon_info); + +errno_t get_pwd_from_pac(TALLOC_CTX *mem_ctx, + struct pac_ctx *pac_ctx, + struct sss_domain_info *dom, + struct PAC_LOGON_INFO *logon_info, + struct passwd **_pwd); #endif /* __PACSRV_H__ */ diff --git a/src/responder/pac/pacsrv_utils.c b/src/responder/pac/pacsrv_utils.c new file mode 100644 index 00000000..43d84121 --- /dev/null +++ b/src/responder/pac/pacsrv_utils.c @@ -0,0 +1,494 @@ +/* + SSSD + + PAC Responder - utility finctions + + Copyright (C) Sumit Bose <sbose@redhat.com> 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 <http://www.gnu.org/licenses/>. +*/ +#include <stdbool.h> +#include <util/data_blob.h> +#include <gen_ndr/security.h> + +#include "util/util.h" +#include "responder/pac/pacsrv.h" + +static errno_t get_rid(struct dom_sid *sid, uint32_t *rid) +{ + if (sid == NULL || sid->num_auths < 1 || rid == NULL) { + return EINVAL; + } + + *rid = sid->sub_auths[sid->num_auths - 1]; + + return EOK; +} + +/** + * Find the Posix ID to a SID from the local IPA domain + */ +errno_t local_sid_to_id(struct local_mapping_ranges *map, struct dom_sid *sid, + uint32_t *id) +{ + int ret; + uint32_t rid; + + if (map == NULL || sid == NULL || id == NULL) { + return EINVAL; + } + + ret = get_rid(sid, &rid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, ("get_rid failed.\n")); + return ret; + } + + if (rid >= map->primary_rids.min && rid <= map->primary_rids.max) { + *id = map->local_ids.min + (rid - map->primary_rids.min); + } else if (rid >= map->secondary_rids.min && + rid <= map->secondary_rids.max) { + *id = map->local_ids.min + (rid - map->secondary_rids.min); + } else { + return ENOENT; + } + + if (*id < map->local_ids.min || *id > map->local_ids.max) { + return ERANGE; + } + + return EOK; +} + +/** + * Add a new remote domain and the corresponding ID range to the context of + * the libsss_idmap. Without this it is not possible to find the Posix UID for + * a user fo the remote domain. + */ +errno_t add_idmap_domain(struct sss_idmap_ctx *idmap_ctx, + struct sysdb_ctx *sysdb, + const char *domain_name, + const char *dom_sid_str) +{ + struct sss_idmap_range range; + enum idmap_error_code err; + + /* TODO: read range form sysdb if + * https://fedorahosted.org/freeipa/ticket/2185 is fixed */ + range.min = 200000; + range.max = 400000; + + err = sss_idmap_add_domain(idmap_ctx, domain_name, dom_sid_str, &range); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, ("sss_idmap_add_domain failed.\n")); + return EFAULT; + } + + return EOK; +} + +/** + * Find the corresponding UID for a user from a remote domain based on the + * domain SID of the remote domain and the RID of the user. + */ +errno_t domsid_rid_to_uid(struct pac_ctx *pac_ctx, + struct sysdb_ctx *sysdb, + const char *domain_name, + struct dom_sid2 *domsid, uint32_t rid, + uid_t *uid) +{ + enum idmap_error_code err; + char *sid_str = NULL; + char *dom_sid_str = NULL; + uint32_t id; + int ret; + + err = sss_idmap_smb_sid_to_sid(pac_ctx->idmap_ctx, domsid, + &dom_sid_str); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, ("sss_idmap_smb_sid_to_sid failed.\n")); + ret = EFAULT; + goto done; + } + + sid_str = talloc_asprintf(NULL, "%s-%lu", dom_sid_str, (unsigned long) rid); + if (sid_str == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("dom_sid_and_rid_string failed.\n")); + return ENOMEM; + } + + err = sss_idmap_smb_sid_to_sid(pac_ctx->idmap_ctx, domsid, &sid_str); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, ("sss_idmap_smb_sid_to_sid failed.\n")); + ret = EFAULT; + goto done; + } + + err = sss_idmap_sid_to_unix(pac_ctx->idmap_ctx, sid_str, &id); + if (err == IDMAP_NO_DOMAIN) { + ret = add_idmap_domain(pac_ctx->idmap_ctx, sysdb, domain_name, + dom_sid_str); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, ("add_idmap_domain failed.\n")); + goto done; + } + + err = sss_idmap_sid_to_unix(pac_ctx->idmap_ctx, sid_str, &id); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_FATAL_FAILURE, ("sss_idmap_sid_to_unix failed " + "even in the second attempt.\n")); + ret = ENOENT; + goto done; + } + } else if (err != IDMAP_SUCCESS && err != IDMAP_NO_DOMAIN) { + DEBUG(SSSDBG_OP_FAILURE, ("sss_idmap_sid_to_unix failed.\n")); + ret = EFAULT; + goto done; + } + + *uid = (uid_t) id; + + ret = EOK; + +done: + talloc_free(dom_sid_str); + talloc_free(sid_str); + return ret; +} + +/** + * Return information about the local domain from the main PAC responder + * context or try to read it from cache and store it in the context. + */ +errno_t get_my_domain_sid(struct pac_ctx *pac_ctx, + struct sss_domain_info *dom, + struct dom_sid **_sid) +{ + struct sysdb_ctx *sysdb; + int ret; + struct ldb_dn *basedn; + const char *attrs[] = {SYSDB_SUBDOMAIN_ID, + NULL}; + size_t msgs_count; + const char *sid_str; + struct ldb_message **msgs; + TALLOC_CTX *tmp_ctx = NULL; + struct dom_sid *sid = NULL; + char *dom_name; + enum idmap_error_code err; + + if (pac_ctx->my_dom_sid == NULL) { + if (dom->parent != NULL) { + sysdb = dom->parent->sysdb; + dom_name = dom->parent->name; + } else { + sysdb = dom->sysdb; + dom_name = dom->name; + } + + if (sysdb == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, ("Missing sysdb context.\n")); + ret = EINVAL; + goto done; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("talloc_new failed.\n")); + ret = ENOMEM; + goto done; + } + + basedn = sysdb_domain_dn(sysdb, tmp_ctx, dom_name); + if (basedn == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_entry(tmp_ctx, sysdb, basedn, LDB_SCOPE_BASE, NULL, + attrs, &msgs_count, &msgs); + if (ret != LDB_SUCCESS) { + ret = EIO; + goto done; + } + + if (msgs_count != 1) { + DEBUG(SSSDBG_OP_FAILURE, ("Base search returned [%d] results, " + "expected 1.\n", msgs_count)); + ret = EINVAL; + goto done; + } + + sid_str = ldb_msg_find_attr_as_string(msgs[0], SYSDB_SUBDOMAIN_ID, NULL); + if (sid_str == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("SID of my domain is not available.\n")); + ret = EINVAL; + goto done; + } + + err = sss_idmap_sid_to_smb_sid(pac_ctx->idmap_ctx, sid_str, &sid); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, ("sss_idmap_sid_to_smb_sid failed.\n")); + ret = EFAULT; + goto done; + } + + pac_ctx->my_dom_sid = talloc_memdup(pac_ctx, sid, + sizeof(struct dom_sid)); + if (pac_ctx->my_dom_sid == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("talloc_memdup failed.\n")); + ret = ENOMEM; + goto done; + } + } + + *_sid = pac_ctx->my_dom_sid; + + ret = EOK; + +done: + talloc_free(sid); + talloc_free(tmp_ctx); + + return ret; +} + +/** + * 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; +} + +/** + * Find all Posix GIDs from a PAC by searching for group SIDs from the local + * domain and convert them to GIDs. + */ +errno_t get_gids_from_pac(TALLOC_CTX *mem_ctx, + struct local_mapping_ranges *range_map, + struct dom_sid *domain_sid, + struct PAC_LOGON_INFO *logon_info, + size_t *_gid_count, gid_t **_gids) +{ + int ret; + size_t g = 0; + size_t s; + struct netr_SamInfo3 *info3; + gid_t *gids = NULL; + + info3 = &logon_info->info3; + + if (info3->sidcount == 0) { + DEBUG(SSSDBG_TRACE_ALL, ("No extra groups found.\n")); + ret = EOK; + goto done; + } + + gids = talloc_array(mem_ctx, gid_t, info3->sidcount); + if (gids == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("talloc_array failed.\n")); + ret = ENOMEM; + goto done; + } + + for(s = 0; s < info3->sidcount; s++) { + if (dom_sid_in_domain(domain_sid, info3->sids[s].sid)) { + ret = local_sid_to_id(range_map, info3->sids[s].sid, &gids[g]); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, ("get_rid failed.\n")); + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, ("Found extra group " + "with gid [%d].\n", gids[g])); + g++; + } + } + + ret = EOK; + +done: + if (ret == EOK) { + *_gid_count = g; + *_gids = gids; + } else { + talloc_free(gids); + } + + 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 pac_ctx *pac_ctx, + struct sss_domain_info *dom, + struct PAC_LOGON_INFO *logon_info, + struct passwd **_pwd) +{ + struct passwd *pwd = NULL; + struct netr_SamBaseInfo *base_info; + int ret; + + 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) { + pwd->pw_name = talloc_strdup(pwd, + base_info->account_name.string); + if (pwd->pw_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("talloc_strdup failed.\n")); + ret = ENOMEM; + goto done; + } + } else { + DEBUG(SSSDBG_OP_FAILURE, ("Missing account name in PAC.\n")); + ret = EINVAL; + goto done; + } + + if (base_info->rid > 0) { + ret = domsid_rid_to_uid(pac_ctx, dom->sysdb, dom->name, + base_info->domain_sid, + base_info->rid, &pwd->pw_uid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, ("domsid_rid_to_uid failed.\n")); + goto done; + } + } else { + DEBUG(SSSDBG_OP_FAILURE, ("Missing user RID in PAC.\n")); + ret = EINVAL; + goto done; + } + + pwd->pw_gid = 0; /* We use MPGs for sub-domains */ + + 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 ")); + } + + if (dom->subdomain_homedir) { + pwd->pw_dir = expand_homedir_template(pwd, dom->subdomain_homedir, + pwd->pw_name, pwd->pw_uid, + dom->name); + if (pwd->pw_dir == NULL) { + ret = ENOMEM; + goto done; + } + } + + pwd->pw_shell = NULL; /* Using default */ + + *_pwd = pwd; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(pwd); + } + + return ret; +} |