summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--daemons/ipa-kdb/ipa_kdb_mspac.c533
1 files changed, 532 insertions, 1 deletions
diff --git a/daemons/ipa-kdb/ipa_kdb_mspac.c b/daemons/ipa-kdb/ipa_kdb_mspac.c
index 799386703..3c48598ce 100644
--- a/daemons/ipa-kdb/ipa_kdb_mspac.c
+++ b/daemons/ipa-kdb/ipa_kdb_mspac.c
@@ -22,9 +22,13 @@
#include "ipa_kdb.h"
#include <talloc.h>
+#include <syslog.h>
#include "util/time.h"
#include "gen_ndr/ndr_krb5pac.h"
+
+int krb5_klog_syslog(int, const char *, ...);
+
static char *user_pac_attrs[] = {
"objectClass",
"uid",
@@ -63,7 +67,9 @@ static char *memberof_pac_attrs[] = {
NULL
};
+#define SID_ID_AUTHS 6
#define SID_SUB_AUTHS 15
+#define MAX(a,b) (((a)>(b))?(a):(b))
static int string_to_sid(char *str, struct dom_sid *sid)
{
@@ -126,6 +132,80 @@ static int string_to_sid(char *str, struct dom_sid *sid)
return 0;
}
+static char *dom_sid_string(TALLOC_CTX *memctx, const struct dom_sid *dom_sid)
+{
+ size_t c;
+ size_t len;
+ int ofs;
+ uint32_t ia;
+ char *buf;
+
+ if (dom_sid == NULL) {
+ return NULL;
+ }
+
+ len = 25 + dom_sid->num_auths * 11;
+
+ buf = talloc_zero_size(memctx, len);
+
+ ia = (dom_sid->id_auth[5]) +
+ (dom_sid->id_auth[4] << 8 ) +
+ (dom_sid->id_auth[3] << 16) +
+ (dom_sid->id_auth[2] << 24);
+
+ ofs = snprintf(buf, len, "S-%u-%lu", (unsigned int) dom_sid->sid_rev_num,
+ (unsigned long) ia);
+
+ for (c = 0; c < dom_sid->num_auths; c++) {
+ ofs += snprintf(buf + ofs, MAX(len - ofs, 0), "-%lu",
+ (unsigned long) dom_sid->sub_auths[c]);
+ }
+
+ if (ofs >= len) {
+ talloc_free(buf);
+ return NULL;
+ }
+
+ return buf;
+}
+
+static struct dom_sid *dom_sid_dup(TALLOC_CTX *memctx,
+ const struct dom_sid *dom_sid)
+{
+ struct dom_sid *new_sid;
+ size_t c;
+
+ if (dom_sid == NULL) {
+ return NULL;
+ }
+
+ new_sid = talloc(memctx, struct dom_sid);
+ if (new_sid == NULL) {
+ return NULL;
+ }
+
+ new_sid->sid_rev_num = dom_sid->sid_rev_num;
+ for (c = 0; c < SID_ID_AUTHS; c++) {
+ new_sid->id_auth[c] = dom_sid->id_auth[c];
+ }
+ new_sid->num_auths = dom_sid->num_auths;
+ for (c = 0; c < SID_SUB_AUTHS; c++) {
+ new_sid->sub_auths[c] = dom_sid->sub_auths[c];
+ }
+
+ return new_sid;
+}
+
+static int sid_append_rid(struct dom_sid *sid, uint32_t rid)
+{
+ if (sid->num_auths >= SID_SUB_AUTHS) {
+ return EINVAL;
+ }
+
+ sid->sub_auths[sid->num_auths++] = rid;
+ return 0;
+}
+
/**
* @brief Takes a user sid and removes the rid.
* The sid is changed by this function,
@@ -143,7 +223,9 @@ static int sid_split_rid(struct dom_sid *sid, uint32_t *rid)
}
sid->num_auths--;
- *rid = sid->sub_auths[sid->num_auths];
+ if (rid != NULL) {
+ *rid = sid->sub_auths[sid->num_auths];
+ }
sid->sub_auths[sid->num_auths] = 0;
return 0;
@@ -538,6 +620,429 @@ static bool is_cross_realm_krbtgt(krb5_const_principal princ)
return true;
}
+static char *gen_sid_string(TALLOC_CTX *memctx, struct dom_sid *dom_sid,
+ uint32_t rid)
+{
+ char *str = NULL;
+ int ret;
+
+ ret = sid_append_rid(dom_sid, rid);
+ if (ret != 0) {
+ krb5_klog_syslog(LOG_ERR, "sid_append_rid failed");
+ return NULL;
+ }
+
+ str = dom_sid_string(memctx, dom_sid);
+ ret = sid_split_rid(dom_sid, NULL);
+ if (ret != 0) {
+ krb5_klog_syslog(LOG_ERR, "sid_split_rid failed");
+ talloc_free(str);
+ return NULL;
+ }
+
+ return str;
+}
+
+static int get_group_sids(TALLOC_CTX *memctx,
+ struct PAC_LOGON_INFO_CTR *logon_info,
+ char ***_group_sids)
+{
+ int ret;
+ size_t c;
+ size_t p = 0;
+ struct dom_sid *domain_sid = NULL;
+ char **group_sids = NULL;
+
+ domain_sid = dom_sid_dup(memctx, logon_info->info->info3.base.domain_sid);
+ if (domain_sid == NULL) {
+ krb5_klog_syslog(LOG_ERR, "dom_sid_dup failed");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ group_sids = talloc_array(memctx, char *,
+ 2 +
+ logon_info->info->info3.base.groups.count +
+ logon_info->info->info3.sidcount);
+ if (group_sids == NULL) {
+ krb5_klog_syslog(LOG_ERR, "talloc_array failed");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ group_sids[p] = gen_sid_string(memctx, domain_sid,
+ logon_info->info->info3.base.primary_gid);
+ if (group_sids[p] == NULL) {
+ krb5_klog_syslog(LOG_ERR, "gen_sid_string failed");
+ ret = EINVAL;
+ goto done;
+ }
+ p++;
+
+ for (c = 0; c < logon_info->info->info3.base.groups.count; c++) {
+ group_sids[p] = gen_sid_string(memctx, domain_sid,
+ logon_info->info->info3.base.groups.rids[c].rid);
+ if (group_sids[p] == NULL) {
+ krb5_klog_syslog(LOG_ERR, "gen_sid_string 2 failed");
+ ret = EINVAL;
+ goto done;
+ }
+ p++;
+ }
+ for (c = 0; c < logon_info->info->info3.sidcount; c++) {
+ group_sids[p] = dom_sid_string(memctx,
+ logon_info->info->info3.sids[c].sid);
+ if (group_sids[p] == NULL) {
+ krb5_klog_syslog(LOG_ERR, "dom_sid_string failed");
+ ret = EINVAL;
+ goto done;
+ }
+ p++;
+ }
+
+ group_sids[p] = NULL;
+
+ *_group_sids = group_sids;
+
+ ret = 0;
+done:
+ talloc_free(domain_sid);
+ if (ret != 0) {
+ talloc_free(group_sids);
+ }
+
+ return ret;
+}
+
+static int add_groups(TALLOC_CTX *memctx,
+ struct PAC_LOGON_INFO_CTR *logon_info,
+ size_t ipa_group_sids_count,
+ struct dom_sid2 *ipa_group_sids)
+{
+ size_t c;
+ struct netr_SidAttr *sids = NULL;
+
+ if (ipa_group_sids_count == 0) {
+ return 0;
+ }
+
+ sids = talloc_realloc(memctx, logon_info->info->info3.sids,
+ struct netr_SidAttr,
+ logon_info->info->info3.sidcount + ipa_group_sids_count);
+ if (sids == NULL) {
+ return ENOMEM;
+ }
+
+
+ for (c = 0; c < ipa_group_sids_count; c++) {
+ sids[c + logon_info->info->info3.sidcount].sid = &ipa_group_sids[c];
+ sids[c + logon_info->info->info3.sidcount].attributes =
+ SE_GROUP_ENABLED |
+ SE_GROUP_MANDATORY |
+ SE_GROUP_ENABLED_BY_DEFAULT;
+ }
+
+ logon_info->info->info3.sidcount += ipa_group_sids_count;
+ logon_info->info->info3.sids = sids;
+
+
+ return 0;
+}
+
+static int map_groups(TALLOC_CTX *memctx, krb5_context kcontext,
+ char **group_sids, size_t *_ipa_group_sids_count,
+ struct dom_sid **_ipa_group_sids)
+{
+ struct ipadb_context *ipactx;
+ krb5_error_code kerr;
+ int ret;
+ LDAPMessage *results = NULL;
+ LDAPMessage *lentry;
+ char *basedn = NULL;
+ char *filter = NULL;
+ LDAPDerefRes *deref_results = NULL;
+ LDAPDerefRes *dres;
+ LDAPDerefVal *dval;
+ size_t c;
+ size_t count = 0;
+ size_t sid_index = 0;
+ struct dom_sid *sids = NULL;
+ char *entry_attrs[] ={"1.1", NULL};
+ unsigned long gid;
+ struct dom_sid sid;
+ char *endptr;
+
+ ipactx = ipadb_get_context(kcontext);
+ if (ipactx == NULL) {
+ return KRB5_KDB_DBNOTINITED;
+ }
+
+ basedn = talloc_asprintf(memctx, "cn=groups,cn=accounts,%s", ipactx->base);
+ if (basedn == NULL) {
+ krb5_klog_syslog(LOG_ERR, "talloc_asprintf failed.");
+ kerr = ENOMEM;
+ goto done;
+ }
+
+ for (c = 0; group_sids[c] != NULL; c++) {
+ talloc_free(filter);
+ filter = talloc_asprintf(memctx, "(&(objectclass=ipaExternalGroup)(ipaExternalMember=%s))",
+ group_sids[c]);
+ if (filter == NULL) {
+ krb5_klog_syslog(LOG_ERR, "talloc_asprintf failed.");
+ kerr = ENOMEM;
+ goto done;
+ }
+
+ kerr = ipadb_deref_search(ipactx, basedn, LDAP_SCOPE_ONE, filter,
+ entry_attrs, deref_search_attrs,
+ memberof_pac_attrs, &results);
+ if (kerr != 0) {
+ krb5_klog_syslog(LOG_ERR, "ipadb_deref_search failed.");
+ goto done;
+ }
+
+ lentry = ldap_first_entry(ipactx->lcontext, results);
+ if (lentry == NULL) {
+ continue;
+ }
+
+ ldap_derefresponse_free(deref_results);
+ ret = ipadb_ldap_deref_results(ipactx->lcontext, lentry, &deref_results);
+ switch (ret) {
+ case ENOENT:
+ /* No entry found, try next SID */
+ break;
+ case 0:
+ if (deref_results == NULL) {
+ krb5_klog_syslog(LOG_ERR, "No results.");
+ break;
+ }
+
+ for (dres = deref_results; dres; dres = dres->next) {
+ count++;
+ }
+
+ sids = talloc_realloc(memctx, sids, struct dom_sid, count);
+ if (sids == NULL) {
+ krb5_klog_syslog(LOG_ERR, "talloc_realloc failed.");
+ kerr = ENOMEM;
+ goto done;
+ }
+
+ for (dres = deref_results; dres; dres = dres->next) {
+ gid = 0;
+ memset(&sid, '\0', sizeof(struct dom_sid));
+ for (dval = dres->attrVals; dval; dval = dval->next) {
+ if (strcasecmp(dval->type, "gidNumber") == 0) {
+ errno = 0;
+ gid = strtoul((char *)dval->vals[0].bv_val,
+ &endptr,10);
+ if (gid == 0 || gid >= UINT32_MAX || errno != 0 ||
+ *endptr != '\0') {
+ continue;
+ }
+ }
+ if (strcasecmp(dval->type,
+ "ipaNTSecurityIdentifier") == 0) {
+ kerr = string_to_sid((char *)dval->vals[0].bv_val, &sid);
+ if (kerr != 0) {
+ continue;
+ }
+ }
+ }
+ if (gid != 0 && sid.sid_rev_num != 0) {
+ /* TODO: check if gid maps to sid */
+ if (sid_index >= count) {
+ krb5_klog_syslog(LOG_ERR, "Index larger than "
+ "array, this shoould "
+ "never happen.");
+ kerr = EFAULT;
+ goto done;
+ }
+ memcpy(&sids[sid_index], &sid, sizeof(struct dom_sid));
+ sid_index++;
+ }
+ }
+
+ break;
+ default:
+ goto done;
+ }
+ }
+
+ *_ipa_group_sids_count = sid_index;
+ *_ipa_group_sids = sids;
+
+ kerr = 0;
+
+done:
+ ldap_derefresponse_free(deref_results);
+ talloc_free(basedn);
+ talloc_free(filter);
+ ldap_msgfree(results);
+ return kerr;
+}
+
+static int read_group_sids_from_env(TALLOC_CTX *memctx,
+ size_t *_ipa_group_sids_count,
+ struct dom_sid **_ipa_group_sids)
+{
+ char *dummy;
+ char *env;
+ char *s;
+ char *endptr;
+ int ret;
+ size_t ipa_group_sids_count = 0;
+ struct dom_sid *ipa_group_sids = NULL;
+ size_t c;
+
+ dummy = getenv("IPA_GROUP_SIDS_FOR_PAC_TESTING");
+ if (dummy == NULL) {
+ ret = 0;
+ goto done;
+ }
+ env = talloc_strdup(memctx, dummy);
+ if (env == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ s = env;
+ while((s = strchr(s, ',')) != NULL) {
+ s++;
+ ipa_group_sids_count++;
+ }
+ ipa_group_sids_count++;
+
+ ipa_group_sids = talloc_array(memctx, struct dom_sid, ipa_group_sids_count);
+ if (ipa_group_sids == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ c = 0;
+ s = env;
+ do {
+ endptr = strchr(s, ',');
+ if (endptr != NULL) {
+ *endptr = '\0';
+ }
+ ret = string_to_sid(s, &ipa_group_sids[c]);
+ if (ret != 0) {
+ krb5_klog_syslog(LOG_ERR, "string_to_sid failed for [%s]", s);
+ goto done;
+ }
+
+ c++;
+ s = endptr;
+ s++;
+ } while(endptr != NULL);
+
+ ret = 0;
+
+done:
+ if (ret == 0) {
+ *_ipa_group_sids_count = ipa_group_sids_count;
+ *_ipa_group_sids = ipa_group_sids;
+ } else {
+ talloc_free(ipa_group_sids);
+ }
+
+ return ret;
+}
+
+static krb5_error_code filter_pac(krb5_context context, krb5_data *old_data,
+ krb5_data *new_data)
+{
+ DATA_BLOB pac_data;
+ union PAC_INFO pac_info;
+ krb5_error_code kerr;
+ enum ndr_err_code ndr_err;
+ TALLOC_CTX *tmpctx;
+ int ret;
+ char **group_sids = NULL;
+ size_t ipa_group_sids_count = 0;
+ struct dom_sid *ipa_group_sids = NULL;
+
+ tmpctx = talloc_new(NULL);
+ if (!tmpctx) {
+ return ENOMEM;
+ }
+
+ pac_data.length = old_data->length;
+ pac_data.data = (uint8_t *) old_data->data;
+
+ ndr_err = ndr_pull_union_blob(&pac_data, tmpctx, &pac_info,
+ PAC_TYPE_LOGON_INFO,
+ (ndr_pull_flags_fn_t) ndr_pull_PAC_INFO);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ krb5_klog_syslog(LOG_ERR, "ndr_pull_union_blob failed");
+ kerr = KRB5_KDB_INTERNAL_ERROR;
+ goto done;
+ }
+
+ ret = get_group_sids(tmpctx, &pac_info.logon_info, &group_sids);
+ if (ret != 0) {
+ krb5_klog_syslog(LOG_ERR, "get_group_sids failed");
+ kerr = KRB5_KDB_INTERNAL_ERROR;
+ goto done;
+ }
+
+ ret = map_groups(tmpctx, context, group_sids, &ipa_group_sids_count,
+ &ipa_group_sids);
+ if (ret != 0) {
+ krb5_klog_syslog(LOG_ERR, "map_groups failed");
+ kerr = KRB5_KDB_INTERNAL_ERROR;
+ goto done;
+ }
+
+ /* FIXME: the following is for testing only, please remove it as soon as
+ * possilbe */
+/*
+ ret = read_group_sids_from_env(tmpctx, &ipa_group_sids_count,
+ &ipa_group_sids);
+ if (ret != 0) {
+ krb5_klog_syslog(LOG_ERR, "read_group_sids_from_env failed");
+ kerr = KRB5_KDB_INTERNAL_ERROR;
+ goto done;
+ }
+*/
+
+ ret = add_groups(tmpctx, &pac_info.logon_info, ipa_group_sids_count,
+ ipa_group_sids);
+ if (ret != 0) {
+ krb5_klog_syslog(LOG_ERR, "add_groups failed");
+ kerr = KRB5_KDB_INTERNAL_ERROR;
+ goto done;
+ }
+
+ ndr_err = ndr_push_union_blob(&pac_data, tmpctx, &pac_info,
+ PAC_TYPE_LOGON_INFO,
+ (ndr_push_flags_fn_t)ndr_push_PAC_INFO);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ krb5_klog_syslog(LOG_ERR, "ndr_push_union_blob failed");
+ kerr = KRB5_KDB_INTERNAL_ERROR;
+ goto done;
+ }
+
+ new_data->magic = KV5M_DATA;
+ new_data->data = malloc(pac_data.length);
+ if (new_data->data == NULL) {
+ kerr = ENOMEM;
+ goto done;
+ }
+ memcpy(new_data->data, pac_data.data, pac_data.length);
+ new_data->length = pac_data.length;
+
+ kerr = 0;
+
+done:
+ talloc_free(tmpctx);
+
+ return kerr;
+}
+
static krb5_error_code ipadb_verify_pac(krb5_context context,
unsigned int flags,
krb5_const_principal client_princ,
@@ -557,6 +1062,7 @@ static krb5_error_code ipadb_verify_pac(krb5_context context,
krb5_pac old_pac = NULL;
krb5_pac new_pac = NULL;
krb5_data data;
+ krb5_data filtered_data;
size_t i;
kerr = krb5_pac_parse(context,
@@ -567,6 +1073,7 @@ static krb5_error_code ipadb_verify_pac(krb5_context context,
goto done;
}
+ memset(&filtered_data, 0, sizeof(filtered_data));
/* for cross realm trusts cases we need to check the right checksum.
* when the PAC is signed by our realm, we can always just check it
* passing our realm krbtgt key as the kdc checksum key (privsvr).
@@ -584,6 +1091,16 @@ static krb5_error_code ipadb_verify_pac(krb5_context context,
/* TODO: Here is where we need to plug our PAC Filtering, later on */
srv_key = krbtgt_key;
+
+ kerr = krb5_pac_get_buffer(context, old_pac, KRB5_PAC_LOGON_INFO, &data);
+ if (kerr != 0) {
+ goto done;
+ }
+
+ kerr = filter_pac(context, &data, &filtered_data);
+ if (kerr != 0) {
+ goto done;
+ }
} else {
/* krbtgt from our own realm */
priv_key = krbtgt_key;
@@ -613,6 +1130,20 @@ static krb5_error_code ipadb_verify_pac(krb5_context context,
buffer_types[i] == KRB5_PAC_PRIVSVR_CHECKSUM) {
continue;
}
+
+ if (buffer_types[i] == KRB5_PAC_LOGON_INFO &&
+ filtered_data.length != 0) {
+ kerr = krb5_pac_add_buffer(context, new_pac,
+ buffer_types[i], &filtered_data);
+ krb5_free_data_contents(context, &filtered_data);
+ if (kerr) {
+ krb5_pac_free(context, new_pac);
+ goto done;
+ }
+
+ continue;
+ }
+
kerr = krb5_pac_get_buffer(context, old_pac,
buffer_types[i], &data);
if (kerr == 0) {