/* SSSD Authors: Sumit Bose 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 "providers/ad/ad_pac.h" #include "util/util.h" #ifdef HAVE_STRUCT_PAC_UPN_DNS_INFO_EX static bool compare_sid_with_dom_sid_and_rid(const struct dom_sid *sid, const struct dom_sid *dom, uint32_t rid) { size_t c; if (sid == NULL || dom == NULL || rid == 0) { return false; } if (sid->sid_rev_num != dom->sid_rev_num) { return false; } for (c = 0; c < sizeof(sid->id_auth); c++) { if (sid->id_auth[c] != dom->id_auth[c]) { return false; } } if (sid->num_auths != dom->num_auths + 1) { return false; } for (c = 0; c < sid->num_auths; c++) { if (c == dom->num_auths) { if (sid->sub_auths[c] != rid) { return false; } } else { if (sid->sub_auths[c] != dom->sub_auths[c]) { return false; } } } return true; } #endif static errno_t check_logon_info_upn_dns_info(const struct PAC_LOGON_INFO *logon_info, const struct PAC_UPN_DNS_INFO *upn_dns_info, const uint32_t pac_check_opts) { const char *delim; if (logon_info == NULL) { return ERR_CHECK_PAC_FAILED; } if (logon_info->info3.base.account_name.string == NULL) { DEBUG(SSSDBG_FUNC_DATA, "Missing account name in PAC.\n"); return ERR_CHECK_PAC_FAILED; } /* If upn_dns_info is not available we have nothing to check. */ if (upn_dns_info == NULL) { if (pac_check_opts & CHECK_PAC_UPN_DNS_INFO_PRESENT) { DEBUG(SSSDBG_FUNC_DATA, "UPN_DNS_INFO pac buffer required, but missing.\n"); return ERR_CHECK_PAC_FAILED; } else { DEBUG(SSSDBG_TRACE_ALL, "upn_dns_info buffer not present, nothing to check.\n"); return EOK; } } /* upn_dns_info has no information which is present in logon_info, so * nothing to check. */ if (upn_dns_info->flags == 0) { DEBUG(SSSDBG_TRACE_ALL, "upn_dns_info buffer has no extra data to check.\n"); return EOK; } /* The user object does not have userPrincipalName set explicitly and the * upn_name is constructed from the user name (sAMAccountName) and the DNS * domain name. Case-insensitive comparison will be used because AD handles * names case-insensitive. */ if ((upn_dns_info->flags & PAC_UPN_DNS_FLAG_CONSTRUCTED) && (pac_check_opts & CHECK_PAC_CHECK_UPN)) { if (upn_dns_info->upn_name == NULL) { DEBUG(SSSDBG_FUNC_DATA, "Missing UPN in PAC.\n"); return ERR_CHECK_PAC_FAILED; } if (upn_dns_info->dns_domain_name == NULL) { DEBUG(SSSDBG_FUNC_DATA, "Missing DNS domain name in PAC.\n"); return ERR_CHECK_PAC_FAILED; } delim = strrchr(upn_dns_info->upn_name, '@'); if (delim == NULL) { DEBUG(SSSDBG_FUNC_DATA, "Missing '@' in UPN [%s] from PAC.\n", upn_dns_info->upn_name); return ERR_CHECK_PAC_FAILED; } if (strcasecmp(delim+1, upn_dns_info->dns_domain_name) != 0) { DEBUG(SSSDBG_FUNC_DATA, "Domain part of UPN [%s] and " "DNS domain name [%s] do not match.\n", upn_dns_info->upn_name, upn_dns_info->dns_domain_name); return ERR_CHECK_PAC_FAILED; } if (strncasecmp(logon_info->info3.base.account_name.string, upn_dns_info->upn_name, (size_t) (delim - upn_dns_info->upn_name)) != 0) { DEBUG(SSSDBG_FUNC_DATA, "Name part of UPN [%s] and account name [%s] " "do not match.\n", upn_dns_info->upn_name, logon_info->info3.base.account_name.string); return ERR_CHECK_PAC_FAILED; } } /* The upn_dns_info is extended with the sAMAccountName and the SID of the * object. */ #ifdef HAVE_STRUCT_PAC_UPN_DNS_INFO_EX if ((upn_dns_info->flags & PAC_UPN_DNS_FLAG_HAS_SAM_NAME_AND_SID) && (pac_check_opts & CHECK_PAC_CHECK_UPN_DNS_INFO_EX)) { if (strcasecmp(logon_info->info3.base.account_name.string, upn_dns_info->ex.sam_name_and_sid.samaccountname) != 0) { DEBUG(SSSDBG_FUNC_DATA, "Account name in LOGON_INFO [%s] and " "UPN_DNS_INFO [%s] PAC buffers do not match.\n", logon_info->info3.base.account_name.string, upn_dns_info->ex.sam_name_and_sid.samaccountname); return ERR_CHECK_PAC_FAILED; } if (!compare_sid_with_dom_sid_and_rid( upn_dns_info->ex.sam_name_and_sid.objectsid, logon_info->info3.base.domain_sid, logon_info->info3.base.rid)) { DEBUG(SSSDBG_FUNC_DATA, "SID from UPN_DNS_INFO PAC buffer " "do not match data from LOGON_INFO buffer.\n"); return ERR_CHECK_PAC_FAILED; } } #else DEBUG(SSSDBG_TRACE_ALL, "This SSSD build does not support the sam_name_and_sid extension of " "the UPN_DNS_INFO pac buffer.\n"); if (pac_check_opts & CHECK_PAC_UPN_DNS_INFO_EX_PRESENT) { DEBUG(SSSDBG_FUNC_DATA, "UPN_DNS_INFO pac buffer extension required, but missing.\n"); return ERR_CHECK_PAC_FAILED; } #endif DEBUG(SSSDBG_TRACE_ALL, "PAC consistency check successful.\n"); return EOK; } errno_t check_upn_and_sid_from_user_and_pac(struct ldb_message *msg, struct sss_idmap_ctx *idmap_ctx, struct PAC_UPN_DNS_INFO *upn_dns_info, const uint32_t pac_check_opts) { const char *user_data; char *pac_ext_sid_str; enum idmap_error_code err; int cmp_ret; if (upn_dns_info == NULL || upn_dns_info->upn_name == NULL) { if (pac_check_opts & CHECK_PAC_UPN_DNS_INFO_PRESENT) { DEBUG(SSSDBG_CRIT_FAILURE, "Missing UPN in PAC.\n"); return ERR_CHECK_PAC_FAILED; } else { DEBUG(SSSDBG_TRACE_ALL, "Missing UPN in PAC, but check is not required.\n"); return EOK; } } else { user_data = ldb_msg_find_attr_as_string(msg, SYSDB_UPN, NULL); /* If the user object doesn't have a UPN we would expect that the UPN in * the PAC_UPN_DNS_INFO buffer is generated and * PAC_UPN_DNS_FLAG_CONSTRUCTED is set. However, there might still be * configurations like 'ldap_user_principal = noSuchAttr' around. So we * just check and log a message. */ if (user_data == NULL && !(upn_dns_info->flags & PAC_UPN_DNS_FLAG_CONSTRUCTED)) { DEBUG(SSSDBG_MINOR_FAILURE, "User object does not have a UPN but PAC " "says otherwise, maybe ldap_user_principal option is set.\n"); if (pac_check_opts & CHECK_PAC_CHECK_UPN) { if (pac_check_opts & CHECK_PAC_CHECK_UPN_ALLOW_MISSING) { DEBUG(SSSDBG_IMPORTANT_INFO, "UPN is missing but PAC UPN check required, " "PAC validation failed. However, " "'check_upn_allow_missing' is set and the error is " "ignored. To make this message go away please check " "why the UPN is not read from the server. In FreeIPA " "environments 'ldap_user_principal' is most probably " "set to a non-existing attribute name to avoid " "issues with enterprise principals. This is not " "needed anymore with recent versions of FreeIPA.\n"); sss_log(SSS_LOG_CRIT, "PAC validation issue, please check " "sssd_pac.log for details"); return EOK; } else { DEBUG(SSSDBG_CRIT_FAILURE, "UPN is missing but PAC UPN check required, " "PAC validation failed.\n"); return ERR_CHECK_PAC_FAILED; } } } if (user_data != NULL) { if (strcasecmp(user_data, upn_dns_info->upn_name) != 0) { if (pac_check_opts & CHECK_PAC_CHECK_UPN) { DEBUG(SSSDBG_CRIT_FAILURE, "UPN of user entry [%s] and " "PAC [%s] do not match.\n", user_data, upn_dns_info->upn_name); return ERR_CHECK_PAC_FAILED; } else { DEBUG(SSSDBG_IMPORTANT_INFO, "UPN of user entry [%s] and " "PAC [%s] do not match, " "ignored.\n", user_data, upn_dns_info->upn_name); return EOK; } } } DEBUG(SSSDBG_TRACE_ALL, "PAC UPN check successful.\n"); } #ifdef HAVE_STRUCT_PAC_UPN_DNS_INFO_EX if (upn_dns_info == NULL || !(upn_dns_info->flags & PAC_UPN_DNS_FLAG_HAS_SAM_NAME_AND_SID) ) { if (pac_check_opts & CHECK_PAC_UPN_DNS_INFO_EX_PRESENT) { DEBUG(SSSDBG_CRIT_FAILURE, "Missing SID in PAC extension.\n"); return ERR_CHECK_PAC_FAILED; } else { DEBUG(SSSDBG_TRACE_ALL, "Missing SID in PAC extension, but check is not required.\n"); return EOK; } } else { user_data = ldb_msg_find_attr_as_string(msg, SYSDB_SID_STR, NULL); if (user_data == NULL) { if (pac_check_opts & CHECK_PAC_CHECK_UPN_DNS_INFO_EX) { DEBUG(SSSDBG_CRIT_FAILURE, "User has no SID stored but SID check is required.\n"); return ERR_CHECK_PAC_FAILED; } else { DEBUG(SSSDBG_TRACE_ALL, "User has no SID stored cannot check SID from PAC.\n"); return EOK; } } err = sss_idmap_smb_sid_to_sid(idmap_ctx, upn_dns_info->ex.sam_name_and_sid.objectsid, &pac_ext_sid_str); if (err != IDMAP_SUCCESS) { DEBUG(SSSDBG_OP_FAILURE, "Failed to convert SID from PAC externsion.\n"); return EIO; } cmp_ret = strcmp(user_data, pac_ext_sid_str); err = sss_idmap_free_sid(idmap_ctx, pac_ext_sid_str); if (err != IDMAP_SUCCESS) { DEBUG(SSSDBG_OP_FAILURE, "sss_idmap_free_sid() failed, ignored.\n"); } if (cmp_ret != 0) { DEBUG(SSSDBG_CRIT_FAILURE, "User SID [%s] and SID from PAC externsion [%s] differ.\n", user_data, pac_ext_sid_str); } } #else DEBUG(SSSDBG_TRACE_ALL, "This SSSD build does not support the sam_name_and_sid extension of " "the UPN_DNS_INFO pac buffer.\n"); if (pac_check_opts & CHECK_PAC_UPN_DNS_INFO_EX_PRESENT) { DEBUG(SSSDBG_FUNC_DATA, "UPN_DNS_INFO pac buffer extension required, but missing.\n"); return ERR_CHECK_PAC_FAILED; } #endif return EOK; } errno_t ad_get_data_from_pac(TALLOC_CTX *mem_ctx, const uint32_t pac_check_opts, uint8_t *pac_blob, size_t pac_len, struct PAC_LOGON_INFO **_logon_info, struct PAC_UPN_DNS_INFO **_upn_dns_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; TALLOC_CTX *tmp_ctx; if (_logon_info != NULL) { *_logon_info = NULL; } if (_upn_dns_info != NULL) { *_upn_dns_info = NULL; } tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); return ENOMEM; } blob.data = pac_blob; blob.length = pac_len; ndr_pull = ndr_pull_init_blob(&blob, tmp_ctx); if (ndr_pull == NULL) { DEBUG(SSSDBG_OP_FAILURE, "ndr_pull_init_blob failed.\n"); ret = ENOMEM; goto done; } ndr_pull->flags |= LIBNDR_FLAG_REF_ALLOC; /* FIXME: is this really needed ? */ pac_data = talloc_zero(tmp_ctx, struct PAC_DATA); if (pac_data == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); ret = ENOMEM; goto done; } 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); ret = EBADMSG; goto done; } for(c = 0; c < pac_data->num_buffers; c++) { switch (pac_data->buffers[c].type) { case PAC_TYPE_SRV_CHECKSUM: break; case PAC_TYPE_KDC_CHECKSUM: break; case PAC_TYPE_LOGON_INFO: if (_logon_info != NULL) { *_logon_info = talloc_steal(mem_ctx, pac_data->buffers[c].info->logon_info.info); } break; case PAC_TYPE_UPN_DNS_INFO: if (_upn_dns_info != NULL) { *_upn_dns_info = talloc_steal(mem_ctx, &pac_data->buffers[c].info->upn_dns_info); } break; default: DEBUG(SSSDBG_TRACE_ALL, "Unhandled PAC buffer type [%d].\n", pac_data->buffers[c].type); } } /* The logon_info buffer is the main PAC buffer with the basic user * information, if this is missing we consider the PAC as broken. */ if (_logon_info != NULL && *_logon_info == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "LOGON_INFO pac buffer missing.\n"); ret = ERR_CHECK_PAC_FAILED; goto done; } /* The upn_dns_info buffer was added with Windows 2008, so there might be * still very old installations which might not have it. But all relevant * Samba versions knows about it, so no ifdef-protection is needed. */ if (_upn_dns_info != NULL && *_upn_dns_info == NULL && ((pac_check_opts & CHECK_PAC_UPN_DNS_INFO_PRESENT) || (pac_check_opts & CHECK_PAC_UPN_DNS_INFO_EX_PRESENT))) { DEBUG(SSSDBG_CRIT_FAILURE, "UPN_DNS_INFO pac buffer required, but missing.\n"); ret = ERR_CHECK_PAC_FAILED; goto done; } #ifdef HAVE_STRUCT_PAC_UPN_DNS_INFO_EX if (_upn_dns_info != NULL && *_upn_dns_info != NULL && !((*_upn_dns_info)->flags & PAC_UPN_DNS_FLAG_HAS_SAM_NAME_AND_SID) && (pac_check_opts & CHECK_PAC_UPN_DNS_INFO_EX_PRESENT)) { DEBUG(SSSDBG_CRIT_FAILURE, "UPN_DNS_INFO pac buffer extension required, but missing.\n"); ret = ERR_CHECK_PAC_FAILED; goto done; } #else DEBUG(SSSDBG_TRACE_ALL, "This SSSD build does not support the sam_name_and_sid extension of " "the UPN_DNS_INFO pac buffer.\n"); if (pac_check_opts & CHECK_PAC_UPN_DNS_INFO_EX_PRESENT) { DEBUG(SSSDBG_FUNC_DATA, "UPN_DNS_INFO pac buffer extension required, but missing.\n"); ret = ERR_CHECK_PAC_FAILED; goto done; } #endif /* Make sure the content of different PAC buffers is consistent. */ if (_logon_info != NULL && _upn_dns_info != NULL) { ret = check_logon_info_upn_dns_info(*_logon_info, *_upn_dns_info, pac_check_opts); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Validating PAC data failed.\n"); goto done; } } ret = EOK; done: talloc_free(tmp_ctx); if (ret != EOK) { if (_logon_info != NULL) { talloc_free(*_logon_info); *_logon_info = NULL; } if (_upn_dns_info != NULL) { talloc_free(*_upn_dns_info); *_upn_dns_info = NULL; } } return ret; }