diff options
Diffstat (limited to 'src/providers/ipa')
-rw-r--r-- | src/providers/ipa/ipa_access.c | 1823 | ||||
-rw-r--r-- | src/providers/ipa/ipa_access.h | 66 | ||||
-rw-r--r-- | src/providers/ipa/ipa_auth.c | 313 | ||||
-rw-r--r-- | src/providers/ipa/ipa_auth.h | 32 | ||||
-rw-r--r-- | src/providers/ipa/ipa_common.c | 597 | ||||
-rw-r--r-- | src/providers/ipa/ipa_common.h | 83 | ||||
-rw-r--r-- | src/providers/ipa/ipa_init.c | 293 | ||||
-rw-r--r-- | src/providers/ipa/ipa_timerules.c | 1186 | ||||
-rw-r--r-- | src/providers/ipa/ipa_timerules.h | 56 |
9 files changed, 4449 insertions, 0 deletions
diff --git a/src/providers/ipa/ipa_access.c b/src/providers/ipa/ipa_access.c new file mode 100644 index 00000000..7dfe1fd9 --- /dev/null +++ b/src/providers/ipa/ipa_access.c @@ -0,0 +1,1823 @@ +/* + SSSD + + IPA Backend Module -- Access control + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2009 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 <http://www.gnu.org/licenses/>. +*/ + +#include <sys/param.h> +#include <security/pam_modules.h> + +#include "util/util.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_access.h" +#include "providers/ipa/ipa_timerules.h" + +#define IPA_HOST_MEMBEROF "memberOf" +#define IPA_HOST_SERVERHOSTNAME "serverHostName" +#define IPA_HOST_FQDN "fqdn" +#define IPA_ACCESS_RULE_TYPE "accessRuleType" +#define IPA_MEMBER_USER "memberUser" +#define IPA_USER_CATEGORY "userCategory" +#define IPA_SERVICE_NAME "serviceName" +#define IPA_SOURCE_HOST "sourceHost" +#define IPA_SOURCE_HOST_CATEGORY "sourceHostCategory" +#define IPA_EXTERNAL_HOST "externalHost" +#define IPA_ACCESS_TIME "accessTime" +#define IPA_UNIQUE_ID "ipauniqueid" +#define IPA_ENABLED_FLAG "ipaenabledflag" +#define IPA_MEMBER_HOST "memberHost" +#define IPA_HOST_CATEGORY "hostCategory" +#define IPA_CN "cn" + +#define IPA_HOST_BASE_TMPL "cn=computers,cn=accounts,dc=%s" +#define IPA_HBAC_BASE_TMPL "cn=hbac,dc=%s" + +#define SYSDB_HBAC_BASE_TMPL "cn=hbac,"SYSDB_TMPL_CUSTOM_BASE + +#define HBAC_RULES_SUBDIR "hbac_rules" +#define HBAC_HOSTS_SUBDIR "hbac_hosts" + +static errno_t msgs2attrs_array(TALLOC_CTX *mem_ctx, size_t count, + struct ldb_message **msgs, + struct sysdb_attrs ***attrs) +{ + int i; + struct sysdb_attrs **a; + + a = talloc_array(mem_ctx, struct sysdb_attrs *, count); + if (a == NULL) { + DEBUG(1, ("talloc_array failed.\n")); + return ENOMEM; + } + + for (i = 0; i < count; i++) { + a[i] = talloc(a, struct sysdb_attrs); + if (a[i] == NULL) { + DEBUG(1, ("talloc_array failed.\n")); + talloc_free(a); + return ENOMEM; + } + a[i]->num = msgs[i]->num_elements; + a[i]->a = talloc_steal(a[i], msgs[i]->elements); + } + + *attrs = a; + + return EOK; +} + +static void ipa_access_reply(struct be_req *be_req, int pam_status) +{ + struct pam_data *pd; + pd = talloc_get_type(be_req->req_data, struct pam_data); + pd->pam_status = pam_status; + + if (pam_status == PAM_SUCCESS) { + be_req->fn(be_req, DP_ERR_OK, pam_status, NULL); + } else { + be_req->fn(be_req, DP_ERR_FATAL, pam_status, NULL); + } +} + +struct hbac_get_user_info_state { + struct tevent_context *ev; + struct be_ctx *be_ctx;; + struct sysdb_handle *handle; + + const char *user; + const char *user_orig_dn; + struct ldb_dn *user_dn; + size_t groups_count; + const char **groups; +}; + +static void search_user_done(struct tevent_req *subreq); +static void search_groups_done(struct tevent_req *subreq); + +struct tevent_req *hbac_get_user_info_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + const char *user) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct hbac_get_user_info_state *state; + int ret; + const char **attrs; + + req = tevent_req_create(memctx, &state, struct hbac_get_user_info_state); + if (req == NULL) { + DEBUG(1, ("tevent_req_create failed.\n")); + return NULL; + } + + state->ev = ev; + state->be_ctx = be_ctx; + state->handle = NULL; + state->user = user; + state->user_orig_dn = NULL; + state->user_dn = NULL; + state->groups_count = 0; + state->groups = NULL; + + attrs = talloc_array(state, const char *, 2); + if (attrs == NULL) { + ret = ENOMEM; + goto fail; + } + + attrs[0] = SYSDB_ORIG_DN; + attrs[1] = NULL; + + subreq = sysdb_search_user_by_name_send(state, ev, be_ctx->sysdb, NULL, + be_ctx->domain, user, attrs); + if (subreq == NULL) { + DEBUG(1, ("sysdb_search_user_by_name_send failed.\n")); + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, search_user_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void search_user_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct hbac_get_user_info_state *state = tevent_req_data(req, + struct hbac_get_user_info_state); + int ret; + const char **attrs; + const char *dummy; + struct ldb_message *user_msg; + + + ret = sysdb_search_user_recv(subreq, state, &user_msg); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + DEBUG(9, ("Found user info for user [%s].\n", state->user)); + state->user_dn = talloc_steal(state, user_msg->dn); + dummy = ldb_msg_find_attr_as_string(user_msg, SYSDB_ORIG_DN, NULL); + if (dummy == NULL) { + DEBUG(1, ("Original DN of user [%s] not available.\n", state->user)); + ret = EINVAL; + goto failed; + } + state->user_orig_dn = talloc_strdup(state, dummy); + if (state->user_dn == NULL) { + DEBUG(1, ("talloc_strdup failed.\n")); + ret = ENOMEM; + goto failed; + } + DEBUG(9, ("Found original DN [%s] for user [%s].\n", state->user_orig_dn, + state->user)); + + attrs = talloc_array(state, const char *, 2); + if (attrs == NULL) { + DEBUG(1, ("talloc_array failed.\n")); + ret = ENOMEM; + goto failed; + } + attrs[0] = SYSDB_ORIG_DN; + attrs[1] = NULL; + + subreq = sysdb_asq_search_send(state, state->ev, state->be_ctx->sysdb, NULL, + state->be_ctx->domain, state->user_dn, NULL, + SYSDB_MEMBEROF, attrs); + if (subreq == NULL) { + DEBUG(1, ("sysdb_asq_search_send failed.\n")); + ret = ENOMEM; + goto failed; + } + + tevent_req_set_callback(subreq, search_groups_done, req); + return; + +failed: + tevent_req_error(req, ret); + return; +} + +static void search_groups_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct hbac_get_user_info_state *state = tevent_req_data(req, + struct hbac_get_user_info_state); + int ret; + int i; + struct ldb_message **msg; + + ret = sysdb_asq_search_recv(subreq, state, &state->groups_count, &msg); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + if (state->groups_count == 0) { + tevent_req_done(req); + return; + } + + state->groups = talloc_array(state, const char *, state->groups_count); + if (state->groups == NULL) { + DEBUG(1, ("talloc_groups failed.\n")); + ret = ENOMEM; + goto failed; + } + + for(i = 0; i < state->groups_count; i++) { + if (msg[i]->num_elements != 1) { + DEBUG(1, ("Unexpected number of elements.\n")); + ret = EINVAL; + goto failed; + } + + if (msg[i]->elements[0].num_values != 1) { + DEBUG(1, ("Unexpected number of values.\n")); + ret = EINVAL; + goto failed; + } + + state->groups[i] = talloc_strndup(state->groups, + (const char *) msg[i]->elements[0].values[0].data, + msg[i]->elements[0].values[0].length); + if (state->groups[i] == NULL) { + DEBUG(1, ("talloc_strndup failed.\n")); + ret = ENOMEM; + goto failed; + } + + DEBUG(9, ("Found group [%s].\n", state->groups[i])); + } + + tevent_req_done(req); + return; + +failed: + talloc_free(state->groups); + tevent_req_error(req, ret); + return; +} + +static int hbac_get_user_info_recv(struct tevent_req *req, TALLOC_CTX *memctx, + const char **user_dn, size_t *groups_count, + const char ***groups) +{ + struct hbac_get_user_info_state *state = tevent_req_data(req, + struct hbac_get_user_info_state); + int i; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *user_dn = talloc_steal(memctx, state->user_orig_dn); + *groups_count = state->groups_count; + for (i = 0; i < state->groups_count; i++) { + talloc_steal(memctx, state->groups[i]); + } + *groups = talloc_steal(memctx, state->groups); + + return EOK; +} + + +struct hbac_get_host_info_state { + struct tevent_context *ev; + struct sdap_id_ctx *sdap_ctx; + struct sysdb_ctx *sysdb; + struct sysdb_handle *handle; + bool offline; + + char *host_filter; + char *host_search_base; + const char **host_attrs; + + struct sysdb_attrs **host_reply_list; + size_t host_reply_count; + size_t current_item; + struct hbac_host_info **hbac_host_info; +}; + +static void hbac_get_host_info_connect_done(struct tevent_req *subreq); +static void hbac_get_host_memberof_done(struct tevent_req *subreq); +static void hbac_get_host_info_sysdb_transaction_started(struct tevent_req *subreq); +static void hbac_get_host_info_store_prepare(struct tevent_req *req); +static void hbac_get_host_info_store_done(struct tevent_req *subreq); + +static struct tevent_req *hbac_get_host_info_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + bool offline, + struct sdap_id_ctx *sdap_ctx, + struct sysdb_ctx *sysdb, + const char *ipa_domain, + const char **hostnames) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct hbac_get_host_info_state *state; + int ret; + int i; + + if (hostnames == NULL || ipa_domain == NULL) { + DEBUG(1, ("Missing hostnames or domain.\n")); + return NULL; + } + + req = tevent_req_create(memctx, &state, struct hbac_get_host_info_state); + if (req == NULL) { + DEBUG(1, ("tevent_req_create failed.\n")); + return NULL; + } + + state->ev = ev; + state->sdap_ctx = sdap_ctx; + state->sysdb = sysdb; + state->handle = NULL; + state->offline = offline; + + state->host_reply_list = NULL; + state->host_reply_count = 0; + state->current_item = 0; + state->hbac_host_info = NULL; + + state->host_filter = talloc_asprintf(state, "(|"); + if (state->host_filter == NULL) { + DEBUG(1, ("Failed to create filter.\n")); + ret = ENOMEM; + goto fail; + } + for (i = 0; hostnames[i] != NULL; i++) { + state->host_filter = talloc_asprintf_append(state->host_filter, + "(&(objectclass=ipaHost)" + "(|(fqdn=%s)(serverhostname=%s)))", + hostnames[i], hostnames[i]); + if (state->host_filter == NULL) { + ret = ENOMEM; + goto fail; + } + } + state->host_filter = talloc_asprintf_append(state->host_filter, ")"); + if (state->host_filter == NULL) { + ret = ENOMEM; + goto fail; + } + + state->host_search_base = talloc_asprintf(state, IPA_HOST_BASE_TMPL, + ipa_domain); + if (state->host_search_base == NULL) { + DEBUG(1, ("Failed to create host search base.\n")); + ret = ENOMEM; + goto fail; + } + + state->host_attrs = talloc_array(state, const char *, 7); + if (state->host_attrs == NULL) { + DEBUG(1, ("Failed to allocate host attribute list.\n")); + ret = ENOMEM; + goto fail; + } + state->host_attrs[0] = IPA_HOST_MEMBEROF; + state->host_attrs[1] = IPA_HOST_SERVERHOSTNAME; + state->host_attrs[2] = IPA_HOST_FQDN; + state->host_attrs[3] = "objectClass"; + state->host_attrs[4] = SYSDB_ORIG_DN; + state->host_attrs[5] = SYSDB_ORIG_MEMBEROF; + state->host_attrs[6] = NULL; + + if (offline) { + subreq = sysdb_search_custom_send(state, state->ev, state->sysdb, NULL, + state->sdap_ctx->be->domain, + state->host_filter, HBAC_HOSTS_SUBDIR, + state->host_attrs); + if (subreq == NULL) { + DEBUG(1, ("sysdb_search_custom_send.\n")); + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, hbac_get_host_memberof_done, req); + + return req; + } + + if (sdap_ctx->gsh == NULL || ! sdap_ctx->gsh->connected) { + if (sdap_ctx->gsh != NULL) { + talloc_zfree(sdap_ctx->gsh); + } + + subreq = sdap_cli_connect_send(state, ev, sdap_ctx->opts, + sdap_ctx->be, sdap_ctx->service, NULL); + if (!subreq) { + DEBUG(1, ("sdap_cli_connect_send failed.\n")); + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, hbac_get_host_info_connect_done, req); + + return req; + } + + subreq = sdap_get_generic_send(state, state->ev, + state->sdap_ctx->opts, + state->sdap_ctx->gsh, + state->host_search_base, + LDAP_SCOPE_SUB, + state->host_filter, + state->host_attrs, + NULL, 0); + + if (subreq == NULL) { + DEBUG(1, ("sdap_get_generic_send failed.\n")); + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, hbac_get_host_memberof_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void hbac_get_host_info_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct hbac_get_host_info_state *state = tevent_req_data(req, + struct hbac_get_host_info_state); + int ret; + + ret = sdap_cli_connect_recv(subreq, state->sdap_ctx, &state->sdap_ctx->gsh, + NULL); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + subreq = sdap_get_generic_send(state, state->ev, + state->sdap_ctx->opts, + state->sdap_ctx->gsh, + state->host_search_base, + LDAP_SCOPE_SUB, + state->host_filter, + state->host_attrs, + NULL, 0); + + if (subreq == NULL) { + DEBUG(1, ("sdap_get_generic_send failed.\n")); + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, hbac_get_host_memberof_done, req); + + return; + +fail: + tevent_req_error(req, ret); + return; +} + +static void hbac_get_host_memberof_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct hbac_get_host_info_state *state = tevent_req_data(req, + struct hbac_get_host_info_state); + int ret; + int i; + int v; + struct ldb_message_element *el; + struct hbac_host_info **hhi; + struct ldb_message **msgs; + + if (state->offline) { + ret = sysdb_search_custom_recv(subreq, state, &state->host_reply_count, + &msgs); + } else { + ret = sdap_get_generic_recv(subreq, state, &state->host_reply_count, + &state->host_reply_list); + } + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + if (state->host_reply_count == 0) { + DEBUG(1, ("No hosts not found in IPA server.\n")); + ret = ENOENT; + goto fail; + } + + if (state->offline) { + ret = msgs2attrs_array(state, state->host_reply_count, msgs, + &state->host_reply_list); + talloc_zfree(msgs); + if (ret != EOK) { + DEBUG(1, ("msgs2attrs_array failed.\n")); + goto fail; + } + } + + hhi = talloc_array(state, struct hbac_host_info *, state->host_reply_count + 1); + if (hhi == NULL) { + DEBUG(1, ("talloc_array failed.\n")); + ret = ENOMEM; + goto fail; + } + memset(hhi, 0, + sizeof(struct hbac_host_info *) * (state->host_reply_count + 1)); + + for (i = 0; i < state->host_reply_count; i++) { + hhi[i] = talloc_zero(hhi, struct hbac_host_info); + if (hhi[i] == NULL) { + ret = ENOMEM; + goto fail; + } + + ret = sysdb_attrs_get_el(state->host_reply_list[i], SYSDB_ORIG_DN, &el); + if (ret != EOK) { + DEBUG(1, ("sysdb_attrs_get_el failed.\n")); + goto fail; + } + if (el->num_values == 0) { + ret = EINVAL; + goto fail; + } + DEBUG(9, ("OriginalDN: [%.*s].\n", el->values[0].length, + (char *)el->values[0].data)); + hhi[i]->dn = talloc_strndup(hhi, (char *)el->values[0].data, + el->values[0].length); + if (hhi[i]->dn == NULL) { + ret = ENOMEM; + goto fail; + } + + ret = sysdb_attrs_get_el(state->host_reply_list[i], + IPA_HOST_SERVERHOSTNAME, &el); + if (ret != EOK) { + DEBUG(1, ("sysdb_attrs_get_el failed.\n")); + goto fail; + } + if (el->num_values == 0) { + ret = EINVAL; + goto fail; + } + DEBUG(9, ("ServerHostName: [%.*s].\n", el->values[0].length, + (char *)el->values[0].data)); + hhi[i]->serverhostname = talloc_strndup(hhi, (char *)el->values[0].data, + el->values[0].length); + if (hhi[i]->serverhostname == NULL) { + ret = ENOMEM; + goto fail; + } + + ret = sysdb_attrs_get_el(state->host_reply_list[i], + IPA_HOST_FQDN, &el); + if (ret != EOK) { + DEBUG(1, ("sysdb_attrs_get_el failed.\n")); + goto fail; + } + if (el->num_values == 0) { + ret = EINVAL; + goto fail; + } + DEBUG(9, ("FQDN: [%.*s].\n", el->values[0].length, + (char *)el->values[0].data)); + hhi[i]->fqdn = talloc_strndup(hhi, (char *)el->values[0].data, + el->values[0].length); + if (hhi[i]->fqdn == NULL) { + ret = ENOMEM; + goto fail; + } + + ret = sysdb_attrs_get_el(state->host_reply_list[i], + state->offline ? SYSDB_ORIG_MEMBEROF : + IPA_HOST_MEMBEROF, + &el); + if (ret != EOK) { + DEBUG(1, ("sysdb_attrs_get_el failed.\n")); + goto fail; + } + + hhi[i]->memberof = talloc_array(hhi, const char *, el->num_values + 1); + if (hhi[i]->memberof == NULL) { + ret = ENOMEM; + goto fail; + } + memset(hhi[i]->memberof, 0, + sizeof(const char *) * (el->num_values + 1)); + + for(v = 0; v < el->num_values; v++) { + DEBUG(9, ("%s: [%.*s].\n", IPA_HOST_MEMBEROF, el->values[v].length, + (const char *)el->values[v].data)); + hhi[i]->memberof[v] = talloc_strndup(hhi, + (const char *)el->values[v].data, + el->values[v].length); + if (hhi[i]->memberof[v] == NULL) { + ret = ENOMEM; + goto fail; + } + } + } + + state->hbac_host_info = hhi; + + if (state->offline) { + tevent_req_done(req); + return; + } + + subreq = sysdb_transaction_send(state, state->ev, state->sysdb); + if (subreq == NULL) { + DEBUG(1, ("sysdb_transaction_send failed.\n")); + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, hbac_get_host_info_sysdb_transaction_started, req); + return; + +fail: + tevent_req_error(req, ret); + return; +} + +static void hbac_get_host_info_sysdb_transaction_started( + struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct hbac_get_host_info_state *state = tevent_req_data(req, + struct hbac_get_host_info_state); + int ret; + + ret = sysdb_transaction_recv(subreq, state, &state->handle); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + state->current_item = 0; + hbac_get_host_info_store_prepare(req); + return; +} + +static void hbac_get_host_info_store_prepare(struct tevent_req *req) +{ + struct hbac_get_host_info_state *state = tevent_req_data(req, + struct hbac_get_host_info_state); + int ret; + char *object_name; + struct ldb_message_element *el; + struct tevent_req *subreq; + + if (state->current_item < state->host_reply_count) { + ret = sysdb_attrs_get_el(state->host_reply_list[state->current_item], + IPA_HOST_FQDN, &el); + if (ret != EOK) { + DEBUG(1, ("sysdb_attrs_get_el failed.\n")); + goto fail; + } + if (el->num_values == 0) { + ret = EINVAL; + goto fail; + } + object_name = talloc_strndup(state, (const char *)el->values[0].data, + el->values[0].length); + if (object_name == NULL) { + ret = ENOMEM; + goto fail; + } + DEBUG(9, ("Fqdn [%s].\n", object_name)); + + + ret = sysdb_attrs_replace_name( + state->host_reply_list[state->current_item], + IPA_HOST_MEMBEROF, SYSDB_ORIG_MEMBEROF); + if (ret != EOK) { + DEBUG(1, ("sysdb_attrs_replace_name failed.\n")); + goto fail; + } + + subreq = sysdb_store_custom_send(state, state->ev, + state->handle, + state->sdap_ctx->be->domain, + object_name, + HBAC_HOSTS_SUBDIR, + state->host_reply_list[state->current_item]); + + if (subreq == NULL) { + DEBUG(1, ("sysdb_store_custom_send failed.\n")); + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, hbac_get_host_info_store_done, req); + return; + } + + subreq = sysdb_transaction_commit_send(state, state->ev, state->handle); + if (subreq == NULL) { + DEBUG(1, ("sysdb_transaction_commit_send failed.\n")); + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, sysdb_transaction_complete, req); + + return; + +fail: + tevent_req_error(req, ret); + return; +} + +static void hbac_get_host_info_store_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct hbac_get_host_info_state *state = tevent_req_data(req, + struct hbac_get_host_info_state); + int ret; + + ret = sysdb_store_custom_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + state->current_item++; + hbac_get_host_info_store_prepare(req); +} + +static int hbac_get_host_info_recv(struct tevent_req *req, TALLOC_CTX *memctx, + struct hbac_host_info ***hhi) +{ + struct hbac_get_host_info_state *state = tevent_req_data(req, + struct hbac_get_host_info_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *hhi = talloc_steal(memctx, state->hbac_host_info); + return EOK; +} + + +struct hbac_get_rules_state { + struct tevent_context *ev; + struct sdap_id_ctx *sdap_ctx; + struct sysdb_ctx *sysdb; + struct sysdb_handle *handle; + bool offline; + + const char *host_dn; + const char **memberof; + char *hbac_filter; + char *hbac_search_base; + const char **hbac_attrs; + + struct ldb_message *old_rules; + struct sysdb_attrs **hbac_reply_list; + size_t hbac_reply_count; + int current_item; +}; + +static void hbac_get_rules_connect_done(struct tevent_req *subreq); +static void hbac_rule_get_done(struct tevent_req *subreq); +static void hbac_rule_sysdb_transaction_started(struct tevent_req *subreq); +static void hbac_rule_sysdb_delete_done(struct tevent_req *subreq); +static void hbac_rule_store_prepare(struct tevent_req *req); +static void hbac_rule_store_done(struct tevent_req *subreq); + +static struct tevent_req *hbac_get_rules_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + bool offline, + struct sdap_id_ctx *sdap_ctx, + struct sysdb_ctx *sysdb, + const char *ipa_domain, + const char *host_dn, + const char **memberof) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct hbac_get_rules_state *state; + int ret; + int i; + + if (host_dn == NULL || ipa_domain == NULL) { + DEBUG(1, ("Missing host_dn or domain.\n")); + return NULL; + } + + req = tevent_req_create(memctx, &state, struct hbac_get_rules_state); + if (req == NULL) { + DEBUG(1, ("tevent_req_create failed.\n")); + return NULL; + } + + state->ev = ev; + state->offline = offline; + state->sdap_ctx = sdap_ctx; + state->sysdb = sysdb; + state->handle = NULL; + state->host_dn = host_dn; + state->memberof = memberof; + + state->old_rules = NULL; + state->hbac_reply_list = NULL; + state->hbac_reply_count = 0; + state->current_item = 0; + + state->hbac_search_base = talloc_asprintf(state, IPA_HBAC_BASE_TMPL, + ipa_domain); + if (state->hbac_search_base == NULL) { + DEBUG(1, ("Failed to create HBAC search base.\n")); + ret = ENOMEM; + goto fail; + } + + state->hbac_attrs = talloc_array(state, const char *, 16); + if (state->hbac_attrs == NULL) { + DEBUG(1, ("Failed to allocate HBAC attribute list.\n")); + ret = ENOMEM; + goto fail; + } + state->hbac_attrs[0] = IPA_ACCESS_RULE_TYPE; + state->hbac_attrs[1] = IPA_MEMBER_USER; + state->hbac_attrs[2] = IPA_USER_CATEGORY; + state->hbac_attrs[3] = IPA_SERVICE_NAME; + state->hbac_attrs[4] = IPA_SOURCE_HOST; + state->hbac_attrs[5] = IPA_SOURCE_HOST_CATEGORY; + state->hbac_attrs[6] = IPA_EXTERNAL_HOST; + state->hbac_attrs[7] = IPA_ACCESS_TIME; + state->hbac_attrs[8] = IPA_UNIQUE_ID; + state->hbac_attrs[9] = IPA_ENABLED_FLAG; + state->hbac_attrs[10] = IPA_CN; + state->hbac_attrs[11] = "objectclass"; + state->hbac_attrs[12] = IPA_MEMBER_HOST; + state->hbac_attrs[13] = IPA_HOST_CATEGORY; + state->hbac_attrs[14] = SYSDB_ORIG_DN; + state->hbac_attrs[15] = NULL; + + state->hbac_filter = talloc_asprintf(state, + "(&(objectclass=ipaHBACRule)" + "(|(%s=%s)(%s=%s)", + IPA_HOST_CATEGORY, "all", + IPA_MEMBER_HOST, host_dn); + if (state->hbac_filter == NULL) { + ret = ENOMEM; + goto fail; + } + for (i = 0; memberof[i] != NULL; i++) { + state->hbac_filter = talloc_asprintf_append(state->hbac_filter, + "(%s=%s)", + IPA_MEMBER_HOST, + memberof[i]); + if (state->hbac_filter == NULL) { + ret = ENOMEM; + goto fail; + } + } + state->hbac_filter = talloc_asprintf_append(state->hbac_filter, "))"); + if (state->hbac_filter == NULL) { + ret = ENOMEM; + goto fail; + } + + DEBUG(9, ("HBAC rule filter: [%s].\n", state->hbac_filter)); + + if (offline) { + subreq = sysdb_search_custom_send(state, state->ev, state->sysdb, NULL, + state->sdap_ctx->be->domain, + state->hbac_filter, HBAC_RULES_SUBDIR, + state->hbac_attrs); + if (subreq == NULL) { + DEBUG(1, ("sysdb_search_custom_send failed.\n")); + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, hbac_rule_get_done, req); + + return req; + } + + if (sdap_ctx->gsh == NULL || ! sdap_ctx->gsh->connected) { + if (sdap_ctx->gsh != NULL) { + talloc_zfree(sdap_ctx->gsh); + } + + subreq = sdap_cli_connect_send(state, ev, sdap_ctx->opts, + sdap_ctx->be, sdap_ctx->service, NULL); + if (!subreq) { + DEBUG(1, ("sdap_cli_connect_send failed.\n")); + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, hbac_get_rules_connect_done, req); + + return req; + } + + subreq = sdap_get_generic_send(state, state->ev, + state->sdap_ctx->opts, + state->sdap_ctx->gsh, + state->hbac_search_base, + LDAP_SCOPE_SUB, + state->hbac_filter, + state->hbac_attrs, + NULL, 0); + + if (subreq == NULL) { + DEBUG(1, ("sdap_get_generic_send failed.\n")); + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, hbac_rule_get_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void hbac_get_rules_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct hbac_get_rules_state *state = tevent_req_data(req, + struct hbac_get_rules_state); + int ret; + + ret = sdap_cli_connect_recv(subreq, state->sdap_ctx, &state->sdap_ctx->gsh, + NULL); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + subreq = sdap_get_generic_send(state, state->ev, + state->sdap_ctx->opts, + state->sdap_ctx->gsh, + state->hbac_search_base, + LDAP_SCOPE_SUB, + state->hbac_filter, + state->hbac_attrs, + NULL, 0); + + if (subreq == NULL) { + DEBUG(1, ("sdap_get_generic_send failed.\n")); + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, hbac_rule_get_done, req); + return; + +fail: + tevent_req_error(req, ret); + return; +} + +static void hbac_rule_get_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct hbac_get_rules_state *state = tevent_req_data(req, + struct hbac_get_rules_state); + int ret; + int i; + struct ldb_message_element *el; + struct ldb_message **msgs; + + if (state->offline) { + ret = sysdb_search_custom_recv(subreq, state, &state->hbac_reply_count, + &msgs); + } else { + ret = sdap_get_generic_recv(subreq, state, &state->hbac_reply_count, + &state->hbac_reply_list); + } + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + if (state->offline) { + ret = msgs2attrs_array(state, state->hbac_reply_count, msgs, + &state->hbac_reply_list); + talloc_zfree(msgs); + if (ret != EOK) { + DEBUG(1, ("msgs2attrs_array failed.\n")); + goto fail; + } + } + + for (i = 0; i < state->hbac_reply_count; i++) { + ret = sysdb_attrs_get_el(state->hbac_reply_list[i], SYSDB_ORIG_DN, &el); + if (ret != EOK) { + DEBUG(1, ("sysdb_attrs_get_el failed.\n")); + goto fail; + } + if (el->num_values == 0) { + DEBUG(1, ("Missing original DN.\n")); + ret = EINVAL; + goto fail; + } + DEBUG(9, ("OriginalDN: [%s].\n", (const char *)el->values[0].data)); + } + + if (state->hbac_reply_count == 0 || state->offline) { + tevent_req_done(req); + return; + } + + subreq = sysdb_transaction_send(state, state->ev, state->sysdb); + if (subreq == NULL) { + DEBUG(1, ("sysdb_transaction_send failed.\n")); + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, hbac_rule_sysdb_transaction_started, req); + return; + +fail: + tevent_req_error(req, ret); + return; +} + +static void hbac_rule_sysdb_transaction_started(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct hbac_get_rules_state *state = tevent_req_data(req, + struct hbac_get_rules_state); + int ret; + struct ldb_dn *hbac_base_dn; + + ret = sysdb_transaction_recv(subreq, state, &state->handle); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + hbac_base_dn = sysdb_custom_subtree_dn(state->sysdb, state, + state->sdap_ctx->be->domain->name, + HBAC_RULES_SUBDIR); + if (hbac_base_dn == NULL) { + ret = ENOMEM; + goto fail; + } + subreq = sysdb_delete_recursive_send(state, state->ev, state->handle, + hbac_base_dn, true); + if (subreq == NULL) { + DEBUG(1, ("sysdb_delete_recursive_send failed.\n")); + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, hbac_rule_sysdb_delete_done, req); + return; + +fail: + tevent_req_error(req, ret); + return; +} + +static void hbac_rule_sysdb_delete_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct hbac_get_rules_state *state = tevent_req_data(req, + struct hbac_get_rules_state); + int ret; + + ret = sysdb_delete_recursive_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + state->current_item = 0; + hbac_rule_store_prepare(req); +} + +static void hbac_rule_store_prepare(struct tevent_req *req) +{ + struct hbac_get_rules_state *state = tevent_req_data(req, + struct hbac_get_rules_state); + int ret; + struct ldb_message_element *el; + struct tevent_req *subreq; + char *object_name; + + if (state->current_item < state->hbac_reply_count) { + + ret = sysdb_attrs_get_el(state->hbac_reply_list[state->current_item], + IPA_UNIQUE_ID, &el); + if (ret != EOK) { + DEBUG(1, ("sysdb_attrs_get_el failed.\n")); + goto fail; + } + if (el->num_values == 0) { + ret = EINVAL; + goto fail; + } + object_name = talloc_strndup(state, (const char *)el->values[0].data, + el->values[0].length); + if (object_name == NULL) { + ret = ENOMEM; + goto fail; + } + DEBUG(9, ("IPAUniqueId: [%s].\n", object_name)); + + subreq = sysdb_store_custom_send(state, state->ev, + state->handle, + state->sdap_ctx->be->domain, + object_name, + HBAC_RULES_SUBDIR, + state->hbac_reply_list[state->current_item]); + + if (subreq == NULL) { + DEBUG(1, ("sysdb_store_custom_send failed.\n")); + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, hbac_rule_store_done, req); + return; + } + + subreq = sysdb_transaction_commit_send(state, state->ev, state->handle); + if (subreq == NULL) { + DEBUG(1, ("sysdb_transaction_commit_send failed.\n")); + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, sysdb_transaction_complete, req); + + return; + +fail: + tevent_req_error(req, ret); + return; +} + +static void hbac_rule_store_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct hbac_get_rules_state *state = tevent_req_data(req, + struct hbac_get_rules_state); + int ret; + + ret = sysdb_store_custom_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + state->current_item++; + hbac_rule_store_prepare(req); +} + +static int hbac_get_rules_recv(struct tevent_req *req, TALLOC_CTX *memctx, + size_t *hbac_rule_count, + struct sysdb_attrs ***hbac_rule_list) +{ + struct hbac_get_rules_state *state = tevent_req_data(req, + struct hbac_get_rules_state); + int i; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *hbac_rule_count = state->hbac_reply_count; + *hbac_rule_list = talloc_steal(memctx, state->hbac_reply_list); + for (i = 0; i < state->hbac_reply_count; i++) { + talloc_steal(memctx, state->hbac_reply_list[i]); + } + return EOK; +} + +enum hbac_result { + HBAC_ALLOW = 1, + HBAC_DENY, + HBAC_NOT_APPLICABLE +}; + +enum check_result { + RULE_APPLICABLE = 0, + RULE_NOT_APPLICABLE, + RULE_ERROR +}; + +enum check_result check_service(struct pam_data *pd, + struct sysdb_attrs *rule_attrs) +{ + int ret; + int i; + struct ldb_message_element *el; + + if (pd->service == NULL) { + DEBUG(1, ("No service in pam data, assuming error.\n")); + return RULE_ERROR; + } + + ret = sysdb_attrs_get_el(rule_attrs, IPA_SERVICE_NAME, &el); + if (ret != EOK) { + DEBUG(1, ("sysdb_attrs_get_el failed.\n")); + return RULE_ERROR; + } + if (el->num_values == 0) { + DEBUG(9, ("No services in rule specified, assuming rule applies.\n")); + return RULE_APPLICABLE; + } else { + for (i = 0; i < el->num_values; i++) { + if (strncasecmp(pd->service, (const char *) el->values[i].data, + el->values[i].length) == 0) { + DEBUG(9, ("Service [%s] found, rule applies.\n", + pd->service)); + return RULE_APPLICABLE; + } + } + DEBUG(9, ("No matching service found, rule does not apply.\n")); + return RULE_NOT_APPLICABLE; + } + + return RULE_ERROR; +} + +enum check_result check_access_time(struct time_rules_ctx *tr_ctx, + struct sysdb_attrs *rule_attrs) +{ + int ret; + int i; + TALLOC_CTX *tmp_ctx = NULL; + struct ldb_message_element *el; + char *rule; + time_t now; + bool result; + + now = time(NULL); + if (now == (time_t) -1) { + DEBUG(1, ("time failed [%d][%s].\n", errno, strerror(errno))); + return RULE_ERROR; + } + + ret = sysdb_attrs_get_el(rule_attrs, IPA_ACCESS_TIME, &el); + if (ret != EOK) { + DEBUG(1, ("sysdb_attrs_get_el failed.\n")); + return RULE_ERROR; + } + if (el->num_values == 0) { + DEBUG(9, ("No access time specified, assuming rule applies.\n")); + return RULE_APPLICABLE; + } else { + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(1, ("talloc_new failed.\n")); + return RULE_ERROR; + } + + for (i = 0; i < el->num_values; i++) { + rule = talloc_strndup(tmp_ctx, (const char *) el->values[i].data, + el->values[i].length); + ret = check_time_rule(tmp_ctx, tr_ctx, rule, now, &result); + if (ret != EOK) { + DEBUG(1, ("check_time_rule failed.\n")); + ret = RULE_ERROR; + goto done; + } + + if (result) { + DEBUG(9, ("Current time [%d] matches rule [%s].\n", now, rule)); + ret = RULE_APPLICABLE; + goto done; + } + } + } + + ret = RULE_NOT_APPLICABLE; + +done: + talloc_free(tmp_ctx); + return ret; +} + +enum check_result check_user(struct hbac_ctx *hbac_ctx, + struct sysdb_attrs *rule_attrs) +{ + int ret; + int i; + int g; + struct ldb_message_element *el; + + if (hbac_ctx->user_dn == NULL) { + DEBUG(1, ("No user DN available, this should never happen.\n")); + return RULE_ERROR; + } + + ret = sysdb_attrs_get_el(rule_attrs, IPA_USER_CATEGORY, &el); + if (ret != EOK) { + DEBUG(1, ("sysdb_attrs_get_el failed.\n")); + return RULE_ERROR; + } + if (el->num_values == 0) { + DEBUG(9, ("USer category is not set.\n")); + } else { + for (i = 0; i < el->num_values; i++) { + if (strncasecmp("all", (const char *) el->values[i].data, + el->values[i].length) == 0) { + DEBUG(9, ("User category is set to 'all', rule applies.\n")); + return RULE_APPLICABLE; + } + DEBUG(9, ("Unsupported user category [%.*s].\n", + el->values[i].length, + (char *) el->values[i].data)); + } + } + + ret = sysdb_attrs_get_el(rule_attrs, IPA_MEMBER_USER, &el); + if (ret != EOK) { + DEBUG(1, ("sysdb_attrs_get_el failed.\n")); + return RULE_ERROR; + } + if (el->num_values == 0) { + DEBUG(9, ("No user specified, rule does not apply.\n")); + return RULE_APPLICABLE; + } else { + for (i = 0; i < el->num_values; i++) { + DEBUG(9, ("Searching matches for [%.*s].\n", el->values[i].length, + (const char *) el->values[i].data)); + DEBUG(9, ("Checking user [%s].\n", hbac_ctx->user_dn)); + if (strncmp(hbac_ctx->user_dn, (const char *) el->values[i].data, + el->values[i].length) == 0) { + DEBUG(9, ("User [%s] found, rule applies.\n", + hbac_ctx->user_dn)); + return RULE_APPLICABLE; + } + + for (g = 0; g < hbac_ctx->groups_count; g++) { + DEBUG(9, ("Checking group [%s].\n", hbac_ctx->groups[g])); + if (strncmp(hbac_ctx->groups[g], + (const char *) el->values[i].data, + el->values[i].length) == 0) { + DEBUG(9, ("Group [%s] found, rule applies.\n", + hbac_ctx->groups[g])); + return RULE_APPLICABLE; + } + } + } + DEBUG(9, ("No matching user found, rule does not apply.\n")); + return RULE_NOT_APPLICABLE; + } + + return RULE_ERROR; +} + +enum check_result check_remote_hosts(const char *rhost, + struct hbac_host_info *hhi, + struct sysdb_attrs *rule_attrs) +{ + int ret; + int i; + int m; + struct ldb_message_element *cat_el; + struct ldb_message_element *src_el; + struct ldb_message_element *ext_el; + + if (hhi == NULL && (rhost == NULL || *rhost == '\0')) { + DEBUG(1, ("No remote host information specified, assuming error.\n")); + return RULE_ERROR; + } + + ret = sysdb_attrs_get_el(rule_attrs, IPA_SOURCE_HOST_CATEGORY, &cat_el); + if (ret != EOK) { + DEBUG(1, ("sysdb_attrs_get_el failed.\n")); + return RULE_ERROR; + } + if (cat_el->num_values == 0) { + DEBUG(9, ("Source host category not set.\n")); + } else { + for(i = 0; i < cat_el->num_values; i++) { + if (strncasecmp("all", (const char *) cat_el->values[i].data, + cat_el->values[i].length) == 0) { + DEBUG(9, ("Source host category is set to 'all', " + "rule applies.\n")); + return RULE_APPLICABLE; + } + DEBUG(9, ("Unsupported source hosts category [%.*s].\n", + cat_el->values[i].length, + (char *) cat_el->values[i].data)); + } + } + + ret = sysdb_attrs_get_el(rule_attrs, IPA_SOURCE_HOST, &src_el); + if (ret != EOK) { + DEBUG(1, ("sysdb_attrs_get_el failed.\n")); + return RULE_ERROR; + } + ret = sysdb_attrs_get_el(rule_attrs, IPA_EXTERNAL_HOST, &ext_el); + if (ret != EOK) { + DEBUG(1, ("sysdb_attrs_get_el failed.\n")); + return RULE_ERROR; + } + + if (src_el->num_values == 0 && ext_el->num_values == 0) { + DEBUG(9, ("No remote host specified in rule, rule does not apply.\n")); + return RULE_NOT_APPLICABLE; + } else { + if (hhi != NULL) { + for (i = 0; i < src_el->num_values; i++) { + if (strncasecmp(hhi->dn, (const char *) src_el->values[i].data, + src_el->values[i].length) == 0) { + DEBUG(9, ("Source host [%s] found, rule applies.\n", + hhi->dn)); + return RULE_APPLICABLE; + } + for (m = 0; hhi->memberof[m] != NULL; m++) { + if (strncasecmp(hhi->memberof[m], + (const char *) src_el->values[i].data, + src_el->values[i].length) == 0) { + DEBUG(9, ("Source host group [%s] found, rule applies.\n", + hhi->memberof[m])); + return RULE_APPLICABLE; + } + } + } + } + + if (rhost != NULL && *rhost != '\0') { + for (i = 0; i < ext_el->num_values; i++) { + if (strncasecmp(rhost, (const char *) ext_el->values[i].data, + ext_el->values[i].length) == 0) { + DEBUG(9, ("External host [%s] found, rule applies.\n", + rhost)); + return RULE_APPLICABLE; + } + } + } + DEBUG(9, ("No matching remote host found.\n")); + return RULE_NOT_APPLICABLE; + } + + return RULE_ERROR; +} + +static errno_t check_if_rule_applies(enum hbac_result *result, + struct hbac_ctx *hbac_ctx, + struct sysdb_attrs *rule_attrs) { + int ret; + struct ldb_message_element *el; + enum hbac_result rule_type; + char *rule_name; + struct pam_data *pd = hbac_ctx->pd; + + ret = sysdb_attrs_get_el(rule_attrs, IPA_CN, &el); + if (ret != EOK) { + DEBUG(1, ("sysdb_attrs_get_el failed.\n")); + return ret; + } + if (el->num_values == 0) { + DEBUG(4, ("rule has no name, assuming '(none)'.\n")); + rule_name = talloc_strdup(rule_attrs, "(none)"); + } else { + rule_name = talloc_strndup(rule_attrs, (const char*) el->values[0].data, + el->values[0].length); + } + if (rule_name == NULL) { + DEBUG(1, ("talloc_strdup failed.\n")); + return ENOMEM; + } + DEBUG(9, ("Processsing rule [%s].\n", rule_name)); + + /* rule type */ + ret = sysdb_attrs_get_el(rule_attrs, IPA_ACCESS_RULE_TYPE, &el); + if (ret != EOK) { + DEBUG(1, ("sysdb_attrs_get_el failed.\n")); + return ret; + } + if (el->num_values == 0) { + DEBUG(4, ("rule has no type, assuming 'deny'.\n")); + rule_type = HBAC_DENY; + } else if (el->num_values == 1) { + if (strncasecmp((const char *) el->values[0].data, "allow", + el->values[0].length) == 0) { + rule_type = HBAC_ALLOW; + } else { + rule_type = HBAC_DENY; + } + } else { + DEBUG(1, ("rule has an unsupported number of values [%d].\n", + el->num_values)); + return EINVAL; + } + + ret = check_service(pd, rule_attrs); + if (ret != RULE_APPLICABLE) { + goto not_applicable; + } + + ret = check_user(hbac_ctx, rule_attrs); + if (ret != RULE_APPLICABLE) { + goto not_applicable; + } + + ret = check_access_time(hbac_ctx->tr_ctx, rule_attrs); + if (ret != RULE_APPLICABLE) { + goto not_applicable; + } + + ret = check_remote_hosts(pd->rhost, hbac_ctx->remote_hhi, rule_attrs); + if (ret != RULE_APPLICABLE) { + goto not_applicable; + } + + *result = rule_type; + + return EOK; + +not_applicable: + if (ret == RULE_NOT_APPLICABLE) { + *result = HBAC_NOT_APPLICABLE; + } else { + *result = HBAC_DENY; + } + return EOK; +} + +static int evaluate_ipa_hbac_rules(struct hbac_ctx *hbac_ctx, + bool *access_allowed) +{ + bool allow_matched = false; + enum hbac_result result; + int ret; + int i; + + *access_allowed = false; + + for (i = 0; i < hbac_ctx->hbac_rule_count ; i++) { + + ret = check_if_rule_applies(&result, hbac_ctx, + hbac_ctx->hbac_rule_list[i]); + if (ret != EOK) { + DEBUG(1, ("check_if_rule_applies failed.\n")); + return ret; + } + + switch (result) { + case HBAC_DENY: + DEBUG(3, ("Access denied by single rule.\n")); + return EOK; + break; + case HBAC_ALLOW: + allow_matched = true; + DEBUG(9, ("Current rule allows access.\n")); + break; + default: + DEBUG(9, ("Current rule does not apply.\n")); + } + + } + + *access_allowed = allow_matched; + + return EOK; +} + +static void hbac_get_host_info_done(struct tevent_req *req); +static void hbac_get_rules_done(struct tevent_req *req); +static void hbac_get_user_info_done(struct tevent_req *req); + +void ipa_access_handler(struct be_req *be_req) +{ + struct tevent_req *req; + struct pam_data *pd; + struct hbac_ctx *hbac_ctx; + int pam_status = PAM_SYSTEM_ERR; + struct ipa_access_ctx *ipa_access_ctx; + const char *hostlist[3]; + + pd = talloc_get_type(be_req->req_data, struct pam_data); + + hbac_ctx = talloc_zero(be_req, struct hbac_ctx); + if (hbac_ctx == NULL) { + DEBUG(1, ("talloc failed.\n")); + goto fail; + } + hbac_ctx->be_req = be_req; + hbac_ctx->pd = pd; + ipa_access_ctx = talloc_get_type( + be_req->be_ctx->bet_info[BET_ACCESS].pvt_bet_data, + struct ipa_access_ctx); + hbac_ctx->sdap_ctx = ipa_access_ctx->sdap_ctx; + hbac_ctx->ipa_options = ipa_access_ctx->ipa_options; + hbac_ctx->tr_ctx = ipa_access_ctx->tr_ctx; + hbac_ctx->offline = be_is_offline(be_req->be_ctx); + + DEBUG(9, ("Connection status is [%s].\n", hbac_ctx->offline ? "offline" : + "online")); + + + hostlist[0] = dp_opt_get_cstring(hbac_ctx->ipa_options, IPA_HOSTNAME); + if (hostlist[0] == NULL) { + DEBUG(1, ("ipa_hostname not available.\n")); + goto fail; + } + if (pd->rhost != NULL && *pd->rhost != '\0') { + hostlist[1] = pd->rhost; + } else { + hostlist[1] = NULL; + pd->rhost = dp_opt_get_string(hbac_ctx->ipa_options, IPA_HOSTNAME); + if (pd->rhost == NULL) { + DEBUG(1, ("ipa_hostname not available.\n")); + goto fail; + } + } + hostlist[2] = NULL; + + req = hbac_get_host_info_send(hbac_ctx, be_req->be_ctx->ev, + hbac_ctx->offline, + hbac_ctx->sdap_ctx, be_req->be_ctx->sysdb, + dp_opt_get_string(hbac_ctx->ipa_options, + IPA_DOMAIN), + hostlist); + if (req == NULL) { + DEBUG(1, ("hbac_get_host_info_send failed.\n")); + goto fail; + } + + tevent_req_set_callback(req, hbac_get_host_info_done, hbac_ctx); + return; + +fail: + ipa_access_reply(be_req, pam_status); +} + +static void hbac_get_host_info_done(struct tevent_req *req) +{ + struct hbac_ctx *hbac_ctx = tevent_req_callback_data(req, struct hbac_ctx); + struct be_req *be_req = hbac_ctx->be_req; + int ret; + int pam_status = PAM_SYSTEM_ERR; + const char *ipa_hostname; + struct hbac_host_info *local_hhi = NULL; + int i; + + ret = hbac_get_host_info_recv(req, hbac_ctx, &hbac_ctx->hbac_host_info); + talloc_zfree(req); + if (ret != EOK) { + goto fail; + } + + ipa_hostname = dp_opt_get_cstring(hbac_ctx->ipa_options, IPA_HOSTNAME); + if (ipa_hostname == NULL) { + DEBUG(1, ("Missing ipa_hostname, this should never happen.\n")); + ret = EINVAL; + goto fail; + } + + for (i = 0; hbac_ctx->hbac_host_info[i] != NULL; i++) { + if (strcmp(hbac_ctx->hbac_host_info[i]->fqdn, ipa_hostname) == 0 || + strcmp(hbac_ctx->hbac_host_info[i]->serverhostname, + ipa_hostname) == 0) { + local_hhi = hbac_ctx->hbac_host_info[i]; + } + if (hbac_ctx->pd->rhost != NULL && *hbac_ctx->pd->rhost != '\0') { + if (strcmp(hbac_ctx->hbac_host_info[i]->fqdn, + hbac_ctx->pd->rhost) == 0 || + strcmp(hbac_ctx->hbac_host_info[i]->serverhostname, + hbac_ctx->pd->rhost) == 0) { + hbac_ctx->remote_hhi = hbac_ctx->hbac_host_info[i]; + } + } + } + if (local_hhi == NULL) { + DEBUG(1, ("Missing host info for [%s].\n", ipa_hostname)); + ret = EINVAL; + goto fail; + } + req = hbac_get_rules_send(hbac_ctx, be_req->be_ctx->ev, hbac_ctx->offline, + hbac_ctx->sdap_ctx, be_req->be_ctx->sysdb, + dp_opt_get_string(hbac_ctx->ipa_options, + IPA_DOMAIN), + local_hhi->dn, local_hhi->memberof); + if (req == NULL) { + DEBUG(1, ("hbac_get_rules_send failed.\n")); + goto fail; + } + + tevent_req_set_callback(req, hbac_get_rules_done, hbac_ctx); + return; + +fail: + ipa_access_reply(be_req, pam_status); +} + +static void hbac_get_rules_done(struct tevent_req *req) +{ + struct hbac_ctx *hbac_ctx = tevent_req_callback_data(req, struct hbac_ctx); + struct pam_data *pd = hbac_ctx->pd; + struct be_req *be_req = hbac_ctx->be_req; + int ret; + int pam_status = PAM_SYSTEM_ERR; + + ret = hbac_get_rules_recv(req, hbac_ctx, &hbac_ctx->hbac_rule_count, + &hbac_ctx->hbac_rule_list); + talloc_zfree(req); + if (ret != EOK) { + goto fail; + } + + req = hbac_get_user_info_send(hbac_ctx, be_req->be_ctx->ev, be_req->be_ctx, + pd->user); + if (req == NULL) { + DEBUG(1, ("hbac_get_user_info_send failed.\n")); + goto fail; + } + + tevent_req_set_callback(req, hbac_get_user_info_done, hbac_ctx); + return; + +fail: + ipa_access_reply(be_req, pam_status); +} + +static void hbac_get_user_info_done(struct tevent_req *req) +{ + struct hbac_ctx *hbac_ctx = tevent_req_callback_data(req, struct hbac_ctx); + struct be_req *be_req = hbac_ctx->be_req; + int ret; + int pam_status = PAM_SYSTEM_ERR; + bool access_allowed = false; + + ret = hbac_get_user_info_recv(req, hbac_ctx, &hbac_ctx->user_dn, + &hbac_ctx->groups_count, + &hbac_ctx->groups); + talloc_zfree(req); + if (ret != EOK) { + goto failed; + } + + ret = evaluate_ipa_hbac_rules(hbac_ctx, &access_allowed); + if (ret != EOK) { + DEBUG(1, ("evaluate_ipa_hbac_rules failed.\n")); + goto failed; + } + + if (access_allowed) { + pam_status = PAM_SUCCESS; + DEBUG(5, ("Access allowed.\n")); + } else { + pam_status = PAM_PERM_DENIED; + DEBUG(3, ("Access denied.\n")); + } + +failed: + ipa_access_reply(be_req, pam_status); +} diff --git a/src/providers/ipa/ipa_access.h b/src/providers/ipa/ipa_access.h new file mode 100644 index 00000000..bd221c57 --- /dev/null +++ b/src/providers/ipa/ipa_access.h @@ -0,0 +1,66 @@ +/* + SSSD + + IPA Backend Module -- Access control + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2009 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef _IPA_ACCESS_H_ +#define _IPA_ACCESS_H_ + +#include "providers/ldap/ldap_common.h" + +enum ipa_access_mode { + IPA_ACCESS_DENY = 0, + IPA_ACCESS_ALLOW +}; + +struct hbac_host_info { + const char *fqdn; + const char *serverhostname; + const char *dn; + const char **memberof; +}; + +struct ipa_access_ctx { + struct sdap_id_ctx *sdap_ctx; + struct dp_option *ipa_options; + struct time_rules_ctx *tr_ctx; +}; + +struct hbac_ctx { + struct sdap_id_ctx *sdap_ctx; + struct dp_option *ipa_options; + struct time_rules_ctx *tr_ctx; + struct be_req *be_req; + struct pam_data *pd; + struct hbac_host_info **hbac_host_info; + struct hbac_host_info *remote_hhi; + struct sysdb_attrs **hbac_rule_list; + size_t hbac_rule_count; + const char *user_dn; + size_t groups_count; + const char **groups; + bool offline; +}; + +void ipa_access_handler(struct be_req *be_req); + +#endif /* _IPA_ACCESS_H_ */ diff --git a/src/providers/ipa/ipa_auth.c b/src/providers/ipa/ipa_auth.c new file mode 100644 index 00000000..86b72e49 --- /dev/null +++ b/src/providers/ipa/ipa_auth.c @@ -0,0 +1,313 @@ +/* + SSSD + + IPA Backend Module -- Authentication + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2009 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 <http://www.gnu.org/licenses/>. +*/ + +#include <sys/param.h> +#include <security/pam_modules.h> + +#include "util/util.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "providers/krb5/krb5_auth.h" +#include "providers/ipa/ipa_common.h" + +struct ipa_auth_ctx { + struct sdap_auth_ctx *sdap_auth_ctx; + struct krb5_ctx *krb5_ctx; + struct be_req *be_req; + be_async_callback_t callback; + void *pvt; + bool password_migration; + + int dp_err_type; + int errnum; + char *errstr; +}; + +static void ipa_auth_reply(struct ipa_auth_ctx *ipa_auth_ctx) +{ + struct pam_data *pd; + struct be_req *be_req = ipa_auth_ctx->be_req; + be_req->fn = ipa_auth_ctx->callback; + be_req->pvt = ipa_auth_ctx->pvt; + be_req->be_ctx->bet_info[BET_AUTH].pvt_bet_data = ipa_auth_ctx->krb5_ctx; + pd = talloc_get_type(be_req->req_data, struct pam_data); + int dp_err_type = ipa_auth_ctx->dp_err_type; + char *errstr = ipa_auth_ctx->errstr; + + talloc_zfree(ipa_auth_ctx); + DEBUG(9, ("sending [%d] [%d] [%s].\n", dp_err_type, pd->pam_status, + errstr)); + + be_req->fn(be_req, dp_err_type, pd->pam_status, errstr); +} + +struct ipa_auth_handler_state { + struct tevent_context *ev; + + int dp_err_type; + int errnum; + char *errstr; +}; + +static void ipa_auth_handler_callback(struct be_req *be_req, + int dp_err_type, + int errnum, + const char *errstr); + +static struct tevent_req *ipa_auth_handler_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct be_req *be_req, + be_req_fn_t auth_handler) +{ + struct ipa_auth_handler_state *state; + struct tevent_req *req; + + req = tevent_req_create(memctx, &state, struct ipa_auth_handler_state); + if (req == NULL) { + DEBUG(1, ("tevent_req_create failed.\n")); + return NULL; + } + + state->ev = ev; + + be_req->fn = ipa_auth_handler_callback; + be_req->pvt = req; + + auth_handler(be_req); + + return req; +} + +static void ipa_auth_handler_callback(struct be_req *be_req, + int dp_err_type, + int errnum, + const char *errstr) +{ + struct tevent_req *req = talloc_get_type(be_req->pvt, struct tevent_req); + struct ipa_auth_handler_state *state = tevent_req_data(req, + struct ipa_auth_handler_state); + + DEBUG(9, ("received from handler [%d] [%d] [%s].\n", dp_err_type, errnum, + errstr)); + state->dp_err_type = dp_err_type; + state->errnum = errnum; + state->errstr = talloc_strdup(state, errstr); + + tevent_req_post(req, state->ev); + tevent_req_done(req); + return; +} + +static int ipa_auth_handler_recv(struct tevent_req *req, TALLOC_CTX *memctx, + int *dp_err_type, int *errnum, + char **errstr) +{ + struct ipa_auth_handler_state *state = tevent_req_data(req, + struct ipa_auth_handler_state); + enum tevent_req_state tstate; + uint64_t err; + + if (tevent_req_is_error(req, &tstate, &err)) { + if (err) return err; + return EIO; + } + + *dp_err_type = state->dp_err_type; + *errnum = state->errnum; + *errstr = talloc_steal(memctx, state->errstr); + + return EOK; +} + + +static void ipa_auth_handler_done(struct tevent_req *req); +static void ipa_auth_ldap_done(struct tevent_req *req); +static void ipa_auth_handler_retry_done(struct tevent_req *req); + +void ipa_auth(struct be_req *be_req) +{ + struct tevent_req *req; + struct ipa_auth_ctx *ipa_auth_ctx; + struct sdap_id_ctx *sdap_id_ctx; + + ipa_auth_ctx = talloc_zero(be_req, struct ipa_auth_ctx); + if (ipa_auth_ctx == NULL) { + DEBUG(1, ("talloc failed.\n")); + be_req->fn(be_req, DP_ERR_FATAL, PAM_SYSTEM_ERR, NULL); + } + + ipa_auth_ctx->callback = be_req->fn; + ipa_auth_ctx->pvt = be_req->pvt; + + ipa_auth_ctx->be_req = be_req; + + ipa_auth_ctx->sdap_auth_ctx = talloc_zero(ipa_auth_ctx, + struct sdap_auth_ctx); + if (ipa_auth_ctx->sdap_auth_ctx == NULL) { + DEBUG(1, ("talloc failed.\n")); + goto fail; + } + + sdap_id_ctx = talloc_get_type( + be_req->be_ctx->bet_info[BET_ID].pvt_bet_data, + struct sdap_id_ctx); + ipa_auth_ctx->sdap_auth_ctx->be = sdap_id_ctx->be; + ipa_auth_ctx->sdap_auth_ctx->opts = sdap_id_ctx->opts; + + ipa_auth_ctx->krb5_ctx = talloc_get_type( + be_req->be_ctx->bet_info[BET_AUTH].pvt_bet_data, + struct krb5_ctx); + +/* TODO: test and activate when server side support is available */ + ipa_auth_ctx->password_migration = false; + + ipa_auth_ctx->dp_err_type = DP_ERR_FATAL; + ipa_auth_ctx->errnum = EIO; + ipa_auth_ctx->errstr = NULL; + + req = ipa_auth_handler_send(ipa_auth_ctx, be_req->be_ctx->ev, be_req, + krb5_pam_handler); + if (req == NULL) { + DEBUG(1, ("ipa_auth_handler_send failed.\n")); + goto fail; + } + + tevent_req_set_callback(req, ipa_auth_handler_done, ipa_auth_ctx); + return; + +fail: + ipa_auth_reply(ipa_auth_ctx); +} + +static void ipa_auth_handler_done(struct tevent_req *req) +{ + struct ipa_auth_ctx *ipa_auth_ctx = tevent_req_callback_data(req, + struct ipa_auth_ctx); + struct pam_data *pd; + struct be_req *be_req; + int ret; + + be_req = ipa_auth_ctx->be_req; + pd = talloc_get_type(be_req->req_data, struct pam_data); + + ret = ipa_auth_handler_recv(req, ipa_auth_ctx, &ipa_auth_ctx->dp_err_type, + &ipa_auth_ctx->errnum, &ipa_auth_ctx->errstr); + talloc_zfree(req); + if (ret != EOK) { + DEBUG(1, ("ipa_auth_handler request failed.\n")); + pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + if (ipa_auth_ctx->dp_err_type != DP_ERR_OK) { + pd->pam_status = ipa_auth_ctx->errnum; + goto done; + } + + if (ipa_auth_ctx->password_migration && pd->pam_status == PAM_CRED_ERR) { + DEBUG(1, ("Assuming Kerberos password is missing, " + "starting password migration.\n")); + be_req->be_ctx->bet_info[BET_AUTH].pvt_bet_data = + ipa_auth_ctx->sdap_auth_ctx; + req = ipa_auth_handler_send(ipa_auth_ctx, be_req->be_ctx->ev, be_req, + sdap_pam_auth_handler); + if (req == NULL) { + DEBUG(1, ("ipa_auth_ldap_send failed.\n")); + goto done; + } + + tevent_req_set_callback(req, ipa_auth_ldap_done, ipa_auth_ctx); + return; + } + +done: + ipa_auth_reply(ipa_auth_ctx); +} + +static void ipa_auth_ldap_done(struct tevent_req *req) +{ + struct ipa_auth_ctx *ipa_auth_ctx = tevent_req_callback_data(req, + struct ipa_auth_ctx); + struct pam_data *pd; + struct be_req *be_req; + int ret; + + be_req = ipa_auth_ctx->be_req; + pd = talloc_get_type(be_req->req_data, struct pam_data); + + ret = ipa_auth_handler_recv(req, ipa_auth_ctx, &ipa_auth_ctx->dp_err_type, + &ipa_auth_ctx->errnum, &ipa_auth_ctx->errstr); + talloc_zfree(req); + if (ret != EOK) { + DEBUG(1, ("ipa_auth_handler request failed.\n")); + pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + if (ipa_auth_ctx->dp_err_type != DP_ERR_OK) { + pd->pam_status = ipa_auth_ctx->errnum; + goto done; + } + + if (pd->pam_status == PAM_SUCCESS) { + DEBUG(1, ("LDAP authentication succeded, " + "trying Kerberos authentication again.\n")); + be_req->be_ctx->bet_info[BET_AUTH].pvt_bet_data = ipa_auth_ctx->krb5_ctx; + req = ipa_auth_handler_send(ipa_auth_ctx, be_req->be_ctx->ev, be_req, + krb5_pam_handler); + if (req == NULL) { + DEBUG(1, ("ipa_auth_ldap_send failed.\n")); + goto done; + } + + tevent_req_set_callback(req, ipa_auth_handler_retry_done, ipa_auth_ctx); + return; + } + +done: + ipa_auth_reply(ipa_auth_ctx); +} + +static void ipa_auth_handler_retry_done(struct tevent_req *req) +{ + struct ipa_auth_ctx *ipa_auth_ctx = tevent_req_callback_data(req, + struct ipa_auth_ctx); + struct pam_data *pd; + struct be_req *be_req; + int ret; + + be_req = ipa_auth_ctx->be_req; + pd = talloc_get_type(be_req->req_data, struct pam_data); + + ret = ipa_auth_handler_recv(req, ipa_auth_ctx, &ipa_auth_ctx->dp_err_type, + &ipa_auth_ctx->errnum, &ipa_auth_ctx->errstr); + talloc_zfree(req); + if (ret != EOK) { + DEBUG(1, ("ipa_auth_handler request failed.\n")); + pd->pam_status = PAM_SYSTEM_ERR; + } + if (ipa_auth_ctx->dp_err_type != DP_ERR_OK) { + pd->pam_status = ipa_auth_ctx->errnum; + } + + ipa_auth_reply(ipa_auth_ctx); +} diff --git a/src/providers/ipa/ipa_auth.h b/src/providers/ipa/ipa_auth.h new file mode 100644 index 00000000..3079bbd1 --- /dev/null +++ b/src/providers/ipa/ipa_auth.h @@ -0,0 +1,32 @@ +/* + SSSD + + IPA Backend Module -- Authentication + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2009 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef _IPA_AUTH_H_ +#define _IPA_AUTH_H_ + +#include "providers/dp_backend.h" + +void ipa_auth(struct be_req *be_req); + +#endif /* _IPA_AUTH_H_ */ diff --git a/src/providers/ipa/ipa_common.c b/src/providers/ipa/ipa_common.c new file mode 100644 index 00000000..7686227a --- /dev/null +++ b/src/providers/ipa/ipa_common.c @@ -0,0 +1,597 @@ +/* + SSSD + + IPA Provider Common Functions + + Authors: + Simo Sorce <ssorce@redhat.com> + + Copyright (C) 2009 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 <http://www.gnu.org/licenses/>. +*/ + +#include <netdb.h> +#include <ctype.h> +#include "providers/ipa/ipa_common.h" + +struct dp_option ipa_basic_opts[] = { + { "ipa_domain", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ipa_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ipa_hostname", DP_OPT_STRING, NULL_STRING, NULL_STRING }, +}; + +struct dp_option ipa_def_ldap_opts[] = { + { "ldap_uri", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_default_bind_dn", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_default_authtok_type", DP_OPT_STRING, NULL_STRING, NULL_STRING}, + { "ldap_default_authtok", DP_OPT_BLOB, NULL_BLOB, NULL_BLOB }, + { "ldap_search_timeout", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER }, + { "ldap_network_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER }, + { "ldap_opt_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER }, + { "ldap_tls_reqcert", DP_OPT_STRING, { "hard" }, NULL_STRING }, + { "ldap_user_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_user_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING }, + { "ldap_user_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_group_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_group_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING }, + { "ldap_group_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_schema", DP_OPT_STRING, { "ipa_v1" }, NULL_STRING }, + { "ldap_offline_timeout", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER }, + { "ldap_force_upper_case_realm", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "ldap_enumeration_refresh_timeout", DP_OPT_NUMBER, { .number = 300 }, NULL_NUMBER }, + { "ldap_purge_cache_timeout", DP_OPT_NUMBER, { .number = 3600 }, NULL_NUMBER }, + { "entry_cache_timeout", DP_OPT_NUMBER, { .number = 1800 }, NULL_NUMBER }, + { "ldap_tls_cacert", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_tls_cacertdir", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_id_use_start_tls", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_sasl_mech", DP_OPT_STRING, { "GSSAPI" } , NULL_STRING }, + { "ldap_sasl_authid", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_krb5_keytab", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_krb5_init_creds", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + /* use the same parm name as the krb5 module so we set it only once */ + { "krb5_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_pwd_policy", DP_OPT_STRING, { "none" } , NULL_STRING }, + { "ldap_referrals", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE } +}; + +struct sdap_attr_map ipa_attr_map[] = { + { "ldap_entry_usn", "entryUSN", SYSDB_USN, NULL }, + { "ldap_rootdse_last_usn", "lastUSN", SYSDB_HIGH_USN, NULL } +}; + +struct sdap_attr_map ipa_user_map[] = { + { "ldap_user_object_class", "posixAccount", SYSDB_USER_CLASS, NULL }, + { "ldap_user_name", "uid", SYSDB_NAME, NULL }, + { "ldap_user_pwd", "userPassword", SYSDB_PWD, NULL }, + { "ldap_user_uid_number", "uidNumber", SYSDB_UIDNUM, NULL }, + { "ldap_user_gid_number", "gidNumber", SYSDB_GIDNUM, NULL }, + { "ldap_user_gecos", "gecos", SYSDB_GECOS, NULL }, + { "ldap_user_home_directory", "homeDirectory", SYSDB_HOMEDIR, NULL }, + { "ldap_user_shell", "loginShell", SYSDB_SHELL, NULL }, + { "ldap_user_principal", "krbPrincipalName", SYSDB_UPN, NULL }, + { "ldap_user_fullname", "cn", SYSDB_FULLNAME, NULL }, + { "ldap_user_member_of", "memberOf", SYSDB_MEMBEROF, NULL }, + { "ldap_user_uuid", "nsUniqueId", SYSDB_UUID, NULL }, + { "ldap_user_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }, + { "ldap_user_shadow_last_change", "shadowLastChange", SYSDB_SHADOWPW_LASTCHANGE, NULL }, + { "ldap_user_shadow_min", "shadowMin", SYSDB_SHADOWPW_MIN, NULL }, + { "ldap_user_shadow_max", "shadowMax", SYSDB_SHADOWPW_MAX, NULL }, + { "ldap_user_shadow_warning", "shadowWarning", SYSDB_SHADOWPW_WARNING, NULL }, + { "ldap_user_shadow_inactive", "shadowInactive", SYSDB_SHADOWPW_INACTIVE, NULL }, + { "ldap_user_shadow_expire", "shadowExpire", SYSDB_SHADOWPW_EXPIRE, NULL }, + { "ldap_user_shadow_flag", "shadowFlag", SYSDB_SHADOWPW_FLAG, NULL }, + { "ldap_user_krb_last_pwd_change", "krbLastPwdChange", SYSDB_KRBPW_LASTCHANGE, NULL }, + { "ldap_user_krb_password_expiration", "krbPasswordExpiration", SYSDB_KRBPW_EXPIRATION, NULL }, + { "ldap_pwd_attribute", "pwdAttribute", SYSDB_PWD_ATTRIBUTE, NULL } +}; + +struct sdap_attr_map ipa_group_map[] = { + { "ldap_group_object_class", "posixGroup", SYSDB_GROUP_CLASS, NULL }, + { "ldap_group_name", "cn", SYSDB_NAME, NULL }, + { "ldap_group_pwd", "userPassword", SYSDB_PWD, NULL }, + { "ldap_group_gid_number", "gidNumber", SYSDB_GIDNUM, NULL }, + { "ldap_group_member", "member", SYSDB_MEMBER, NULL }, + { "ldap_group_uuid", "nsUniqueId", SYSDB_UUID, NULL }, + { "ldap_group_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL } +}; + +struct dp_option ipa_def_krb5_opts[] = { + { "krb5_kdcip", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_ccachedir", DP_OPT_STRING, { "/tmp" }, NULL_STRING }, + { "krb5_ccname_template", DP_OPT_STRING, { "FILE:%d/krb5cc_%U_XXXXXX" }, NULL_STRING}, + { "krb5_changepw_principal", DP_OPT_STRING, { "kadmin/changepw" }, NULL_STRING }, + { "krb5_auth_timeout", DP_OPT_NUMBER, { .number = 15 }, NULL_NUMBER }, + { "krb5_keytab", DP_OPT_STRING, { "/etc/krb5.keytab" }, NULL_STRING }, + { "krb5_validate", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE } +}; + +int domain_to_basedn(TALLOC_CTX *memctx, const char *domain, char **basedn) +{ + const char *s; + char *dn; + char *p; + int l; + + s = domain; + dn = talloc_strdup(memctx, "dc="); + + while ((p = strchr(s, '.'))) { + l = p - s; + dn = talloc_asprintf_append_buffer(dn, "%.*s,dc=", l, s); + if (!dn) { + return ENOMEM; + } + s = p + 1; + } + dn = talloc_strdup_append_buffer(dn, s); + if (!dn) { + return ENOMEM; + } + + *basedn = dn; + return EOK; +} + +int ipa_get_options(TALLOC_CTX *memctx, + struct confdb_ctx *cdb, + const char *conf_path, + struct sss_domain_info *dom, + struct ipa_options **_opts) +{ + struct ipa_options *opts; + char *domain; + char *server; + char *ipa_hostname; + int ret; + char hostname[HOST_NAME_MAX + 1]; + + opts = talloc_zero(memctx, struct ipa_options); + if (!opts) return ENOMEM; + + ret = dp_get_options(opts, cdb, conf_path, + ipa_basic_opts, + IPA_OPTS_BASIC, + &opts->basic); + if (ret != EOK) { + goto done; + } + + domain = dp_opt_get_string(opts->basic, IPA_DOMAIN); + if (!domain) { + ret = dp_opt_set_string(opts->basic, IPA_DOMAIN, dom->name); + if (ret != EOK) { + goto done; + } + } + + /* FIXME: Make non-fatal once we have discovery */ + server = dp_opt_get_string(opts->basic, IPA_SERVER); + if (!server) { + DEBUG(0, ("Can't find ipa server, missing option!\n")); + ret = EINVAL; + goto done; + } + + ipa_hostname = dp_opt_get_string(opts->basic, IPA_HOSTNAME); + if (ipa_hostname == NULL) { + ret = gethostname(hostname, HOST_NAME_MAX); + if (ret != EOK) { + DEBUG(1, ("gethostname failed [%d][%s].\n", errno, + strerror(errno))); + ret = errno; + goto done; + } + hostname[HOST_NAME_MAX] = '\0'; + DEBUG(9, ("Setting ipa_hostname to [%s].\n", hostname)); + ret = dp_opt_set_string(opts->basic, IPA_HOSTNAME, hostname); + if (ret != EOK) { + goto done; + } + } + + + ret = EOK; + *_opts = opts; + +done: + if (ret != EOK) { + talloc_zfree(opts); + } + return ret; +} + +int ipa_get_id_options(struct ipa_options *ipa_opts, + struct confdb_ctx *cdb, + const char *conf_path, + struct sdap_options **_opts) +{ + TALLOC_CTX *tmpctx; + char *hostname; + char *basedn; + char *realm; + char *value; + int ret; + int i; + + /* self check test, this should never fail, unless someone forgot + * to properly update the code after new ldap options have been added */ + if (SDAP_OPTS_BASIC != IPA_OPTS_BASIC_TEST) { + DEBUG(0, ("Option numbers do not match (%d != %d)\n", + SDAP_OPTS_BASIC, IPA_OPTS_BASIC_TEST)); + abort(); + } + + tmpctx = talloc_new(ipa_opts); + if (!tmpctx) { + return ENOMEM; + } + + ipa_opts->id = talloc_zero(ipa_opts, struct sdap_options); + if (!ipa_opts->id) { + ret = ENOMEM; + goto done; + } + + /* get sdap options */ + ret = dp_get_options(ipa_opts->id, cdb, conf_path, + ipa_def_ldap_opts, + SDAP_OPTS_BASIC, + &ipa_opts->id->basic); + if (ret != EOK) { + goto done; + } + + if (NULL == dp_opt_get_string(ipa_opts->id->basic, SDAP_SEARCH_BASE)) { + ret = domain_to_basedn(tmpctx, + dp_opt_get_string(ipa_opts->basic, IPA_DOMAIN), + &basedn); + if (ret != EOK) { + goto done; + } + + /* FIXME: get values by querying IPA */ + /* set search base */ + value = talloc_asprintf(tmpctx, "cn=accounts,%s", basedn); + if (!value) { + ret = ENOMEM; + goto done; + } + ret = dp_opt_set_string(ipa_opts->id->basic, + SDAP_SEARCH_BASE, value); + if (ret != EOK) { + goto done; + } + DEBUG(6, ("Option %s set to %s\n", + ipa_opts->id->basic[SDAP_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->id->basic, SDAP_SEARCH_BASE))); + } + + /* set the ldap_sasl_authid if the ipa_hostname override was specified */ + if (NULL == dp_opt_get_string(ipa_opts->id->basic, SDAP_SASL_AUTHID)) { + hostname = dp_opt_get_string(ipa_opts->basic, IPA_HOSTNAME); + if (hostname) { + value = talloc_asprintf(tmpctx, "host/%s", hostname); + if (!value) { + ret = ENOMEM; + goto done; + } + ret = dp_opt_set_string(ipa_opts->id->basic, + SDAP_SASL_AUTHID, value); + if (ret != EOK) { + goto done; + } + } + DEBUG(6, ("Option %s set to %s\n", + ipa_opts->id->basic[SDAP_SASL_AUTHID].opt_name, + dp_opt_get_string(ipa_opts->id->basic, SDAP_SASL_AUTHID))); + } + + /* set krb realm */ + if (NULL == dp_opt_get_string(ipa_opts->id->basic, SDAP_KRB5_REALM)) { + realm = dp_opt_get_string(ipa_opts->basic, IPA_DOMAIN); + for (i = 0; realm[i]; i++) { + realm[i] = toupper(realm[i]); + } + ret = dp_opt_set_string(ipa_opts->id->basic, + SDAP_KRB5_REALM, realm); + if (ret != EOK) { + goto done; + } + DEBUG(6, ("Option %s set to %s\n", + ipa_opts->id->basic[SDAP_KRB5_REALM].opt_name, + dp_opt_get_string(ipa_opts->id->basic, SDAP_KRB5_REALM))); + } + + /* fix schema to IPAv1 for now */ + ipa_opts->id->schema_type = SDAP_SCHEMA_IPA_V1; + + /* set user/group search bases if they are not specified */ + if (NULL == dp_opt_get_string(ipa_opts->id->basic, + SDAP_USER_SEARCH_BASE)) { + ret = dp_opt_set_string(ipa_opts->id->basic, SDAP_USER_SEARCH_BASE, + dp_opt_get_string(ipa_opts->id->basic, + SDAP_SEARCH_BASE)); + if (ret != EOK) { + goto done; + } + DEBUG(6, ("Option %s set to %s\n", + ipa_opts->id->basic[SDAP_USER_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->id->basic, + SDAP_USER_SEARCH_BASE))); + } + + if (NULL == dp_opt_get_string(ipa_opts->id->basic, + SDAP_GROUP_SEARCH_BASE)) { + ret = dp_opt_set_string(ipa_opts->id->basic, SDAP_GROUP_SEARCH_BASE, + dp_opt_get_string(ipa_opts->id->basic, + SDAP_SEARCH_BASE)); + if (ret != EOK) { + goto done; + } + DEBUG(6, ("Option %s set to %s\n", + ipa_opts->id->basic[SDAP_GROUP_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->id->basic, + SDAP_GROUP_SEARCH_BASE))); + } + + ret = sdap_get_map(ipa_opts->id, cdb, conf_path, + ipa_attr_map, + SDAP_AT_GENERAL, + &ipa_opts->id->gen_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(ipa_opts->id, + cdb, conf_path, + ipa_user_map, + SDAP_OPTS_USER, + &ipa_opts->id->user_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(ipa_opts->id, + cdb, conf_path, + ipa_group_map, + SDAP_OPTS_GROUP, + &ipa_opts->id->group_map); + if (ret != EOK) { + goto done; + } + + ret = EOK; + *_opts = ipa_opts->id; + +done: + talloc_zfree(tmpctx); + if (ret != EOK) { + talloc_zfree(ipa_opts->id); + } + return ret; +} + +/* the following define is used to keep track of * the options in the krb5 + * module, so that if they change and ipa is not updated correspondingly + * this will trigger a runtime abort error */ +#define IPA_KRB5_OPTS_TEST 8 + +int ipa_get_auth_options(struct ipa_options *ipa_opts, + struct confdb_ctx *cdb, + const char *conf_path, + struct dp_option **_opts) +{ + char *value; + int ret; + int i; + + /* self check test, this should never fail, unless someone forgot + * to properly update the code after new ldap options have been added */ + if (KRB5_OPTS != IPA_KRB5_OPTS_TEST) { + DEBUG(0, ("Option numbers do not match (%d != %d)\n", + KRB5_OPTS, IPA_KRB5_OPTS_TEST)); + abort(); + } + + ipa_opts->auth = talloc_zero(ipa_opts, struct dp_option); + if (ipa_opts->auth == NULL) { + ret = ENOMEM; + goto done; + } + + /* get krb5 options */ + ret = dp_get_options(ipa_opts, cdb, conf_path, + ipa_def_krb5_opts, + KRB5_OPTS, &ipa_opts->auth); + if (ret != EOK) { + goto done; + } + + /* set krb realm */ + if (NULL == dp_opt_get_string(ipa_opts->auth, KRB5_REALM)) { + value = dp_opt_get_string(ipa_opts->basic, IPA_DOMAIN); + if (!value) { + ret = ENOMEM; + goto done; + } + for (i = 0; value[i]; i++) { + value[i] = toupper(value[i]); + } + ret = dp_opt_set_string(ipa_opts->auth, KRB5_REALM, value); + if (ret != EOK) { + goto done; + } + DEBUG(6, ("Option %s set to %s\n", + ipa_opts->auth[KRB5_REALM].opt_name, + dp_opt_get_string(ipa_opts->auth, KRB5_REALM))); + } + + *_opts = ipa_opts->auth; + ret = EOK; + +done: + if (ret != EOK) { + talloc_zfree(ipa_opts->auth); + } + return ret; +} + +static void ipa_resolve_callback(void *private_data, struct fo_server *server) +{ + struct ipa_service *service; + struct hostent *srvaddr; + char *address; + char *new_uri; + int ret; + + service = talloc_get_type(private_data, struct ipa_service); + if (!service) { + DEBUG(1, ("FATAL: Bad private_data\n")); + return; + } + + srvaddr = fo_get_server_hostent(server); + if (!srvaddr) { + DEBUG(1, ("FATAL: No hostent available for server (%s)\n", + fo_get_server_name(server))); + return; + } + + address = talloc_asprintf(service, "%s", srvaddr->h_name); + if (!address) { + DEBUG(1, ("Failed to copy address ...\n")); + return; + } + + new_uri = talloc_asprintf(service, "ldap://%s", address); + if (!new_uri) { + DEBUG(2, ("Failed to copy URI ...\n")); + talloc_free(address); + return; + } + + /* free old one and replace with new one */ + talloc_zfree(service->sdap->uri); + service->sdap->uri = new_uri; + talloc_zfree(service->krb5_service->address); + service->krb5_service->address = address; + + ret = write_kdcinfo_file(service->krb5_service->realm, address); + if (ret != EOK) { + DEBUG(2, ("write_kdcinfo_file failed, authentication might fail.\n")); + } + +} + +int ipa_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx, + const char *servers, const char *domain, + struct ipa_service **_service) +{ + TALLOC_CTX *tmp_ctx; + struct ipa_service *service; + char **list = NULL; + char *realm; + int ret; + int i; + + tmp_ctx = talloc_new(memctx); + if (!tmp_ctx) { + return ENOMEM; + } + + service = talloc_zero(tmp_ctx, struct ipa_service); + if (!service) { + ret = ENOMEM; + goto done; + } + service->sdap = talloc_zero(service, struct sdap_service); + if (!service->sdap) { + ret = ENOMEM; + goto done; + } + service->krb5_service = talloc_zero(service, struct krb5_service); + if (!service->krb5_service) { + ret = ENOMEM; + goto done; + } + + ret = be_fo_add_service(ctx, "IPA"); + if (ret != EOK) { + DEBUG(1, ("Failed to create failover service!\n")); + goto done; + } + + service->sdap->name = talloc_strdup(service, "IPA"); + if (!service->sdap->name) { + ret = ENOMEM; + goto done; + } + + service->krb5_service->name = talloc_strdup(service, "IPA"); + if (!service->krb5_service->name) { + ret = ENOMEM; + goto done; + } + + realm = talloc_strdup(service, domain); + if (!realm) { + ret = ENOMEM; + goto done; + } + for (i = 0; realm[i]; i++) { + realm[i] = toupper(realm[i]); + } + service->krb5_service->realm = realm; + + /* split server parm into a list */ + ret = split_on_separator(tmp_ctx, servers, ',', true, &list, NULL); + if (ret != EOK) { + DEBUG(1, ("Failed to parse server list!\n")); + goto done; + } + + /* now for each one add a new server to the failover service */ + for (i = 0; list[i]; i++) { + + talloc_steal(service, list[i]); + + ret = be_fo_add_server(ctx, "IPA", list[i], 0, NULL); + if (ret && ret != EEXIST) { + DEBUG(0, ("Failed to add server\n")); + goto done; + } + + DEBUG(6, ("Added Server %s\n", list[i])); + } + + ret = be_fo_service_add_callback(memctx, ctx, "IPA", + ipa_resolve_callback, service); + if (ret != EOK) { + DEBUG(1, ("Failed to add failover callback!\n")); + goto done; + } + + ret = EOK; + +done: + if (ret == EOK) { + *_service = talloc_steal(memctx, service); + } + talloc_zfree(tmp_ctx); + return ret; +} + diff --git a/src/providers/ipa/ipa_common.h b/src/providers/ipa/ipa_common.h new file mode 100644 index 00000000..60c7313f --- /dev/null +++ b/src/providers/ipa/ipa_common.h @@ -0,0 +1,83 @@ +/* + SSSD + + IPA Common utility code + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2009 + + 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/>. +*/ + +#ifndef _IPA_COMMON_H_ +#define _IPA_COMMON_H_ + +#include "util/util.h" +#include "confdb/confdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/krb5/krb5_common.h" + +struct ipa_service { + struct sdap_service *sdap; + struct krb5_service *krb5_service; +}; + +/* the following define is used to keep track of the options in the ldap + * module, so that if they change and ipa is not updated correspondingly + * this will trigger a runtime abort error */ +#define IPA_OPTS_BASIC_TEST 31 + +enum ipa_basic_opt { + IPA_DOMAIN = 0, + IPA_SERVER, + IPA_HOSTNAME, + + IPA_OPTS_BASIC /* opts counter */ +}; + +struct ipa_options { + struct dp_option *basic; + + struct ipa_service *service; + + /* id provider */ + struct sdap_options *id; + struct sdap_id_ctx *id_ctx; + + /* auth and chpass provider */ + struct dp_option *auth; + struct krb5_ctx *auth_ctx; +}; + +/* options parsers */ +int ipa_get_options(TALLOC_CTX *memctx, + struct confdb_ctx *cdb, + const char *conf_path, + struct sss_domain_info *dom, + struct ipa_options **_opts); + +int ipa_get_id_options(struct ipa_options *ipa_opts, + struct confdb_ctx *cdb, + const char *conf_path, + struct sdap_options **_opts); + +int ipa_get_auth_options(struct ipa_options *ipa_opts, + struct confdb_ctx *cdb, + const char *conf_path, + struct dp_option **_opts); + +int ipa_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx, + const char *servers, const char *domain, + struct ipa_service **_service); + +#endif /* _IPA_COMMON_H_ */ diff --git a/src/providers/ipa/ipa_init.c b/src/providers/ipa/ipa_init.c new file mode 100644 index 00000000..10b9257a --- /dev/null +++ b/src/providers/ipa/ipa_init.c @@ -0,0 +1,293 @@ +/* + SSSD + + IPA Provider Initialization functions + + Authors: + Simo Sorce <ssorce@redhat.com> + + Copyright (C) 2009 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 <http://www.gnu.org/licenses/>. +*/ + +#include <sys/types.h> +#include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "providers/child_common.h" +#include "providers/ipa/ipa_common.h" +#include "providers/krb5/krb5_auth.h" +#include "providers/ipa/ipa_auth.h" +#include "providers/ipa/ipa_access.h" +#include "providers/ipa/ipa_timerules.h" + +struct ipa_options *ipa_options = NULL; + +/* Id Handler */ +struct bet_ops ipa_id_ops = { + .handler = sdap_account_info_handler, + .finalize = NULL +}; + +struct bet_ops ipa_auth_ops = { + .handler = ipa_auth, + .finalize = NULL, +}; + +struct bet_ops ipa_chpass_ops = { + .handler = krb5_pam_handler, + .finalize = NULL, +}; + +struct bet_ops ipa_access_ops = { + .handler = ipa_access_handler, + .finalize = NULL +}; + +int common_ipa_init(struct be_ctx *bectx) +{ + const char *ipa_servers; + const char *ipa_domain; + int ret; + + ret = ipa_get_options(bectx, bectx->cdb, + bectx->conf_path, + bectx->domain, &ipa_options); + if (ret != EOK) { + return ret; + } + + ipa_servers = dp_opt_get_string(ipa_options->basic, IPA_SERVER); + if (!ipa_servers) { + DEBUG(0, ("Missing ipa_server option!\n")); + return EINVAL; + } + + ipa_domain = dp_opt_get_string(ipa_options->basic, IPA_DOMAIN); + if (!ipa_domain) { + DEBUG(0, ("Missing ipa_domain option!\n")); + return EINVAL; + } + + ret = ipa_service_init(ipa_options, bectx, ipa_servers, ipa_domain, + &ipa_options->service); + if (ret != EOK) { + DEBUG(0, ("Failed to init IPA failover service!\n")); + return ret; + } + + return EOK; +} + +int sssm_ipa_init(struct be_ctx *bectx, + struct bet_ops **ops, + void **pvt_data) +{ + struct sdap_id_ctx *ctx; + int ret; + + if (!ipa_options) { + ret = common_ipa_init(bectx); + if (ret != EOK) { + return ret; + } + } + + if (ipa_options->id_ctx) { + /* already initialized */ + *ops = &ipa_id_ops; + *pvt_data = ipa_options->id_ctx; + return EOK; + } + + ctx = talloc_zero(ipa_options, struct sdap_id_ctx); + if (!ctx) { + return ENOMEM; + } + ctx->be = bectx; + ctx->service = ipa_options->service->sdap; + ipa_options->id_ctx = ctx; + + ret = ipa_get_id_options(ipa_options, bectx->cdb, + bectx->conf_path, + &ctx->opts); + if (ret != EOK) { + goto done; + } + + ret = setup_tls_config(ctx->opts->basic); + if (ret != EOK) { + DEBUG(1, ("setup_tls_config failed [%d][%s].\n", + ret, strerror(ret))); + goto done; + } + + ret = sdap_id_setup_tasks(ctx); + if (ret != EOK) { + goto done; + } + + ret = setup_child(ctx); + if (ret != EOK) { + DEBUG(1, ("setup_child failed [%d][%s].\n", + ret, strerror(ret))); + goto done; + } + + *ops = &ipa_id_ops; + *pvt_data = ctx; + ret = EOK; + +done: + if (ret != EOK) { + talloc_zfree(ipa_options->id_ctx); + } + return ret; +} + +int sssm_ipa_auth_init(struct be_ctx *bectx, + struct bet_ops **ops, + void **pvt_data) +{ + struct krb5_ctx *ctx; + struct tevent_signal *sige; + FILE *debug_filep; + unsigned v; + int ret; + + if (!ipa_options) { + ret = common_ipa_init(bectx); + if (ret != EOK) { + return ret; + } + } + + if (ipa_options->auth_ctx) { + /* already initialized */ + *ops = &ipa_auth_ops; + *pvt_data = ipa_options->auth_ctx; + return EOK; + } + + ctx = talloc_zero(bectx, struct krb5_ctx); + if (!ctx) { + return ENOMEM; + } + ctx->service = ipa_options->service->krb5_service; + ipa_options->auth_ctx = ctx; + + ret = ipa_get_auth_options(ipa_options, bectx->cdb, + bectx->conf_path, + &ctx->opts); + if (ret != EOK) { + goto done; + } + + ret = check_and_export_options(ctx->opts, bectx->domain); + if (ret != EOK) { + DEBUG(1, ("check_and_export_opts failed.\n")); + goto done; + } + + sige = tevent_add_signal(bectx->ev, ctx, SIGCHLD, SA_SIGINFO, + child_sig_handler, NULL); + if (sige == NULL) { + DEBUG(1, ("tevent_add_signal failed.\n")); + ret = ENOMEM; + goto done; + } + + if (debug_to_file != 0) { + ret = open_debug_file_ex("krb5_child", &debug_filep); + if (ret != EOK) { + DEBUG(0, ("Error setting up logging (%d) [%s]\n", + ret, strerror(ret))); + goto done; + } + + ctx->child_debug_fd = fileno(debug_filep); + if (ctx->child_debug_fd == -1) { + DEBUG(0, ("fileno failed [%d][%s]\n", errno, strerror(errno))); + ret = errno; + goto done; + } + + v = fcntl(ctx->child_debug_fd, F_GETFD, 0); + fcntl(ctx->child_debug_fd, F_SETFD, v & ~FD_CLOEXEC); + } + + *ops = &ipa_auth_ops; + *pvt_data = ctx; + ret = EOK; + +done: + if (ret != EOK) { + talloc_zfree(ipa_options->auth_ctx); + } + return ret; +} + +int sssm_ipa_chpass_init(struct be_ctx *bectx, + struct bet_ops **ops, + void **pvt_data) +{ + int ret; + ret = sssm_ipa_auth_init(bectx, ops, pvt_data); + *ops = &ipa_chpass_ops; + return ret; +} + +int sssm_ipa_access_init(struct be_ctx *bectx, + struct bet_ops **ops, + void **pvt_data) +{ + int ret; + struct ipa_access_ctx *ipa_access_ctx; + + ipa_access_ctx = talloc_zero(bectx, struct ipa_access_ctx); + if (ipa_access_ctx == NULL) { + DEBUG(1, ("talloc_zero failed.\n")); + return ENOMEM; + } + + ret = sssm_ipa_init(bectx, ops, (void **) &ipa_access_ctx->sdap_ctx); + if (ret != EOK) { + DEBUG(1, ("sssm_ipa_init failed.\n")); + goto done; + } + + ret = dp_copy_options(ipa_access_ctx, ipa_options->basic, + IPA_OPTS_BASIC, &ipa_access_ctx->ipa_options); + if (ret != EOK) { + DEBUG(1, ("dp_copy_options failed.\n")); + goto done; + } + + ret = init_time_rules_parser(ipa_access_ctx, &ipa_access_ctx->tr_ctx); + if (ret != EOK) { + DEBUG(1, ("init_time_rules_parser failed.\n")); + goto done; + } + + *ops = &ipa_access_ops; + *pvt_data = ipa_access_ctx; + +done: + if (ret != EOK) { + talloc_free(ipa_access_ctx); + } + return ret; +} diff --git a/src/providers/ipa/ipa_timerules.c b/src/providers/ipa/ipa_timerules.c new file mode 100644 index 00000000..1a52eef1 --- /dev/null +++ b/src/providers/ipa/ipa_timerules.c @@ -0,0 +1,1186 @@ +/* + SSSD + + IPA Provider Time Rules Parsing + + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) Red Hat, Inc 2009 + + 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/>. +*/ + +#define _XOPEN_SOURCE /* strptime() needs this */ + +#include <pcre.h> +#include <talloc.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <time.h> +#include <stdbool.h> +#include <limits.h> + +#include "providers/ipa/ipa_timerules.h" +#include "util/util.h" + +#define JMP_NEOK(variable) do { \ + if (variable != EOK) goto done; \ +} while (0) + +#define JMP_NEOK_LABEL(variable, label) do { \ + if (variable != EOK) goto label; \ +} while (0) + +#define CHECK_PTR(ptr) do { \ + if (ptr == NULL) { \ + return ENOMEM; \ + } \ +} while (0) + +#define CHECK_PTR_JMP(ptr) do { \ + if (ptr == NULL) { \ + ret = ENOMEM; \ + goto done; \ + } \ +} while (0) + +#define BUFFER_OR_JUMP(ctx, ptr, count) do { \ + ptr = talloc_array(ctx, unsigned char, count); \ + if (ptr == NULL) { \ + return ENOMEM; \ + } \ + memset(ptr, 0, sizeof(unsigned char)*count); \ +} while (0) + +#define TEST_BIT_RANGE(bitfield, index, resptr) do { \ + if (bitfield) { \ + if (test_bit(&bitfield, index) == 0) { \ + *resptr = false; \ + return EOK; \ + } \ + } \ +} while (0) + +#define TEST_BIT_RANGE_PTR(bitfield, index, resptr) do { \ + if (bitfield) { \ + if (test_bit(bitfield, index) == 0) { \ + *resptr = false; \ + return EOK; \ + } \ + } \ +} while (0) + +/* number of match offsets when matching pcre regexes */ +#define OVEC_SIZE 30 + +/* regular expressions describing syntax of our HBAC grammar */ +#define RGX_WEEKLY "day (?P<day_of_week>(0|1|2|3|4|5|6|7|Mon|Tue|Wed|Thu|Fri|Sat|Sun|,|-)+)" + +#define RGX_MDAY "(?P<mperspec_day>day) (?P<interval_day>[0-9,-]+) " +#define RGX_MWEEK "(?P<mperspec_week>week) (?P<interval_week>[0-9,-]+) "RGX_WEEKLY +#define RGX_MONTHLY RGX_MDAY"|"RGX_MWEEK + +#define RGX_YDAY "(?P<yperspec_day>day) (?P<day_of_year>[0-9,-]+) " +#define RGX_YWEEK "(?P<yperspec_week>week) (?P<week_of_year>[0-9,-]+) "RGX_WEEKLY +#define RGX_YMONTH "(?P<yperspec_month>month) (?P<month_number>[0-9,-]+) (?P<m_period>.*?)$" +#define RGX_YEARLY RGX_YMONTH"|"RGX_YWEEK"|"RGX_YDAY + +#define RGX_TIMESPEC "(?P<timeFrom>[0-9]{4}) ~ (?P<timeTo>[0-9]{4})" + +#define RGX_GENERALIZED "(?P<year>[0-9]{4})(?P<month>[0-9]{2})(?P<day>[0-9]{2})(?P<hour>[0-9]{2})?(?P<minute>[0-9]{2})?(?P<second>[0-9]{2})?" + +#define RGX_PERIODIC "^periodic (?P<perspec>daily|weekly|monthly|yearly) (?P<period>.*?)"RGX_TIMESPEC"$" +#define RGX_ABSOLUTE "^absolute (?P<from>\\S+) ~ (?P<to>\\S+)$" + +/* limits on various parameters */ +#define DAY_OF_WEEK_MAX 7 +#define DAY_OF_MONTH_MAX 31 +#define WEEK_OF_MONTH_MAX 5 +#define WEEK_OF_YEAR_MAX 54 +#define DAY_OF_YEAR_MAX 366 +#define MONTH_MAX 12 +#define HOUR_MAX 23 +#define MINUTE_MAX 59 + +/* limits on sizes of buffers for bit arrays */ +#define DAY_OF_MONTH_BUFSIZE 8 +#define DAY_OF_YEAR_BUFSIZE 44 +#define WEEK_OF_YEAR_BUFSIZE 13 +#define MONTH_BUFSIZE 2 +#define HOUR_BUFSIZE 4 +#define MINUTE_BUFSIZE 8 + +/* Lookup tables for translating names of days and months */ +static const char *names_day_of_week[] = + { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", NULL }; +static const char *names_months[] = + { "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Nov", "Dec", NULL }; + +/* + * Timelib knows two types of ranges - periodic and absolute + */ +enum rangetypes { + TYPE_ABSOLUTE, + TYPE_PERIODIC +}; + +struct absolute_range { + time_t time_from; + time_t time_to; +}; + +struct periodic_range { + unsigned char day_of_week; + unsigned char *day_of_month; + unsigned char *day_of_year; + unsigned char week_of_month; + unsigned char *week_of_year; + unsigned char *month; + unsigned char *hour; + unsigned char *minute; +}; + +/* + * Context of one time rule being analyzed + */ +struct range_ctx { + /* main context with precompiled patterns */ + struct time_rules_ctx *trctx; + /* enum rangetypes */ + enum rangetypes type; + + struct absolute_range *abs; + struct periodic_range *per; +}; + + +/* + * The context of one regular expression + */ +struct parse_ctx { + /* the regular expression used for one parsing */ + pcre *re; + /* number of matches */ + int matches; + /* vector of matches */ + int *ovec; +}; + +/* indexes to the array of precompiled regexes */ +enum timelib_rgx { + LP_RGX_GENERALIZED, + LP_RGX_MDAY, + LP_RGX_MWEEK, + LP_RGX_YEARLY, + LP_RGX_WEEKLY, + LP_RGX_ABSOLUTE, + LP_RGX_PERIODIC, + LP_RGX_MAX, +}; + +/* matches the indexes */ +static const char *lookup_table[] = { + RGX_GENERALIZED, + RGX_MDAY, + RGX_MWEEK, + RGX_YEARLY, + RGX_WEEKLY, + RGX_ABSOLUTE, + RGX_PERIODIC, + NULL, +}; + +/* + * Main struct passed outside + * holds precompiled regular expressions + */ +struct time_rules_ctx { + pcre *re[LP_RGX_MAX]; +}; + +/******************************************************************* + * helper function - bit arrays * + *******************************************************************/ + +/* set a single bit in a bitmap */ +static void set_bit(unsigned char *bitmap, unsigned int bit) +{ + bitmap[bit/CHAR_BIT] |= 1 << (bit%CHAR_BIT); +} + +/* + * This function is based on bit_nset macro written originally by Paul Vixie, + * copyrighted by The Regents of the University of California, as found + * in tarball of fcron, file bitstring.h + */ +static void set_bit_range(unsigned char *bitmap, unsigned int start, + unsigned int stop) +{ + int startbyte = start/CHAR_BIT; + int stopbyte = stop/CHAR_BIT; + + if (startbyte == stopbyte) { + bitmap[startbyte] |= ((0xff << (start & 0x7)) & + (0xff >> (CHAR_BIT- 1 - (stop & 0x7)))); + } else { + bitmap[startbyte] |= 0xff << (start & 0x7); + while (++startbyte < stopbyte) { + bitmap[startbyte] |= 0xff; + } + bitmap[stopbyte] |= 0xff >> (CHAR_BIT- 1 - (stop & 0x7)); + } +} + +static int test_bit(unsigned char *bitmap, unsigned int bit) +{ + return (int)(bitmap[bit/CHAR_BIT] >> (bit%CHAR_BIT)) & 1; +} + +/******************************************************************* + * parsing intervals * + *******************************************************************/ + +/* + * Some ranges allow symbolic names, like Mon..Sun for names of day. + * This routine takes a list of symbolic names as NAME_ARRAY and the + * one we're looking for as KEY and returns its index or -1 when not + * found. The last member of NAME_ARRAY must be NULL. + */ +static int name_index(const char **name_array, const char *key, int min) +{ + int index = 0; + const char *one; + + if (name_array == NULL) { + return -1; + } + + while ((one = name_array[index]) != NULL) { + if (strcmp(key,one) == 0) { + return index+min; + } + index++; + } + + return -1; +} + +/* + * Sets appropriate bits given by an interval in STR (in form of 1,5-7,10) to + * a bitfield given in OUT. Does no boundary checking. STR can also contain + * symbolic names, these would be given in TRANSLATE. + */ +static int interval2bitfield(TALLOC_CTX *mem_ctx, + unsigned char *out, + const char *str, + int min, int max, + const char **translate) +{ + char *copy; + char *next, *token; + int tokval, tokmax; + char *end_ptr; + int ret; + char *dash; + + DEBUG(9, ("Converting '%s' to interval\n", str)); + + copy = talloc_strdup(mem_ctx, str); + CHECK_PTR(copy); + + next = copy; + while (next) { + token = next; + next = strchr(next, ','); + if (next) { + *next = '\0'; + next++; + } + + errno = 0; + tokval = strtol(token, &end_ptr, 10); + if (*end_ptr == '\0' && errno == 0) { + if (tokval <= max && tokval >= 0) { + set_bit(out, tokval); + continue; + } else { + ret = ERANGE; + goto done; + } + } else if ((dash = strchr(token, '-')) != NULL){ + *dash = '\0'; + ++dash; + + errno = 0; + tokval = strtol(token, &end_ptr, 10); + if (*end_ptr != '\0' || errno != 0) { + tokval = name_index(translate, token, min); + if (tokval == -1) { + ret = ERANGE; + goto done; + } + } + errno = 0; + tokmax = strtol(dash, &end_ptr, 10); + if (*end_ptr != '\0' || errno != 0) { + tokmax = name_index(translate, dash, min); + if (tokmax == -1) { + ret = ERANGE; + goto done; + } + } + + if (tokval <= max && tokmax <= max && + tokval >= min && tokmax >= min) { + if (tokmax > tokval) { + DEBUG(7, ("Setting interval %d-%d\n", tokval, tokmax)); + DEBUG(9, ("interval: %p\n", out)); + set_bit_range(out, tokval, tokmax); + } else { + /* Interval wraps around - i.e. from 18.00 to 06.00 */ + DEBUG(7, ("Setting inverted interval %d-%d\n", tokval, tokmax)); + DEBUG(9, ("interval: %p\n", out)); + set_bit_range(out, min, tokmax); + set_bit_range(out, tokval, max); + } + continue; + } else { + /* tokval or tokmax are not between <min, max> */ + ret = ERANGE; + goto done; + } + } else if ((tokval = name_index(translate, token, min)) != -1) { + /* Try to translate one token by name */ + if (tokval <= max) { + set_bit(out, tokval); + continue; + } else { + ret = ERANGE; + goto done; + } + } else { + ret = EINVAL; + goto done; + } + } + + ret = EOK; +done: + talloc_free(copy); + return ret; +} + +/******************************************************************* + * wrappers around regexp handling * + *******************************************************************/ + +/* + * Copies a named substring SUBSTR_NAME from string STR using the parsing + * information from PCTX. The context PCTX is also used as a talloc context. + * + * The resulting string is stored in OUT. + * Return value is EOK on no error or ENOENT on error capturing the substring + */ +static int copy_substring(struct parse_ctx *pctx, + const char *str, + const char *substr_name, + char **out) +{ + const char *result = NULL; + int ret; + char *o = NULL; + + result = NULL; + + ret = pcre_get_named_substring(pctx->re, str, pctx->ovec, + pctx->matches, substr_name, &result); + if (ret < 0 || result == NULL) { + DEBUG(5, ("named substring '%s' does not exist in '%s'\n", + substr_name, str)); + return ENOENT; + } + + o = talloc_strdup(pctx, result); + pcre_free_substring(result); + if (o == NULL) { + return ENOMEM; + } + + DEBUG(9, ("Copied substring named '%s' value '%s'\n", substr_name, o)); + + *out = o; + return EOK; +} + +/* + * Copies a named substring SUBSTR_NAME from string STR using the parsing + * information from PCTX and converts it to an integer. + * The context PCTX is also used as a talloc context. + * + * The resulting string is stored in OUT. + * Return value is EOK on no error or ENOENT on error capturing the substring + */ +static int substring_strtol(struct parse_ctx *pctx, + const char *str, + const char *substr_name, + int *out) +{ + char *substr = NULL; + int ret; + int val; + char *err_ptr; + + ret = copy_substring(pctx, str, substr_name, &substr); + if (ret != EOK) { + DEBUG(5, ("substring '%s' does not exist\n", substr_name)); + return ret; + } + + errno = 0; + val = strtol(substr, &err_ptr, 10); + if (substr == '\0' || *err_ptr != '\0' || errno != 0) { + DEBUG(5, ("substring '%s' does not contain an integerexist\n", + substr)); + talloc_free(substr); + return EINVAL; + } + + *out = val; + talloc_free(substr); + return EOK; +} + +/* + * Compiles a regular expression REGEXP and tries to match it against the + * string STR. Fills in structure _PCTX with info about matching. + * + * Returns EOK on no error, EFAULT on bad regexp, EINVAL when it cannot + * match the regexp. + */ +static int matches_regexp(TALLOC_CTX *ctx, + struct time_rules_ctx *trctx, + const char *str, + enum timelib_rgx regex, + struct parse_ctx **_pctx) +{ + int ret; + struct parse_ctx *pctx = NULL; + + pctx = talloc_zero(ctx, struct parse_ctx); + CHECK_PTR(pctx); + pctx->ovec = talloc_array(pctx, int, OVEC_SIZE); + CHECK_PTR_JMP(pctx->ovec); + pctx->re = trctx->re[regex]; + + ret = pcre_exec(pctx->re, NULL, str, strlen(str), 0, PCRE_NOTEMPTY, pctx->ovec, OVEC_SIZE); + if (ret <= 0) { + DEBUG(8, ("string '%s' did *NOT* match regexp '%s'\n", str, lookup_table[regex])); + ret = EINVAL; + goto done; + } + DEBUG(8, ("string '%s' matched regexp '%s'\n", str, lookup_table[regex])); + + pctx->matches = ret; + *_pctx = pctx; + return EOK; + +done: + talloc_free(pctx); + return ret; +} + +/******************************************************************* + * date/time helper functions * + *******************************************************************/ + +/* + * Returns week number as an integer + * This may seem ugly, but I think it's actually less error prone + * than writing my own routine + */ +static int weeknum(const struct tm *t) +{ + char buf[3]; + + if (!strftime(buf, 3, "%U", t)) { + return -1; + } + + /* %U returns 0-53, we want 1-54 */ + return atoi(buf)+1; +} + +/* + * Return the week of the month + * Range is 1 to 5 + */ +static int get_week_of_month(const struct tm *t) +{ + int fs; /* first sunday */ + + fs = (t->tm_mday % 7) - t->tm_wday; + if (fs <= 0) { + fs += 7; + } + + return (t->tm_mday <= fs) ? 1 : (2 + (t->tm_mday - fs - 1) / 7); +} + +/* + * Normalize differencies between our HBAC definition and semantics of + * struct tm + */ +static void abs2tm(struct tm *t) +{ + /* tm defines tm_year as num of yrs since 1900, we have absolute number */ + t->tm_year %= 1900; + /* struct tm defines tm_mon as number of month since January */ + t->tm_mon--; +} + +/* + * Normalize differencies between our HBAC definition and semantics of + * struct tm + */ +static void tm2abs(struct tm *t) +{ + /* tm defines tm_year as num of yrs since 1900, we have absolute number */ + t->tm_year += 1900; + /* struct tm defines tm_mon as number of month since January */ + t->tm_mon++; +} + +/******************************************************************* + * parsing of HBAC rules themselves * + *******************************************************************/ + +/* + * Parses generalized time string given in STR and fills the + * information into OUT. + */ +static int parse_generalized_time(struct parse_ctx *pctx, + struct time_rules_ctx *trctx, + const char *str, + time_t *out) +{ + int ret; + struct parse_ctx *gctx = NULL; + struct tm tm; + + memset(&tm, 0, sizeof(tm)); + tm.tm_isdst = -1; + + ret = matches_regexp(pctx, trctx, str, LP_RGX_GENERALIZED, &gctx); + JMP_NEOK(ret); + + /* compulsory */ + ret = substring_strtol(gctx, str, "year", &tm.tm_year); + JMP_NEOK(ret); + ret = substring_strtol(gctx, str, "month", &tm.tm_mon); + JMP_NEOK(ret); + ret = substring_strtol(gctx, str, "day", &tm.tm_mday); + JMP_NEOK(ret); + /* optional */ + ret = substring_strtol(gctx, str, "hour", &tm.tm_hour); + JMP_NEOK_LABEL(ret, enoent); + ret = substring_strtol(gctx, str, "minute", &tm.tm_min); + JMP_NEOK_LABEL(ret, enoent); + ret = substring_strtol(gctx, str, "second", &tm.tm_sec); + JMP_NEOK_LABEL(ret, enoent); + +enoent: + if (ret == ENOENT) { + ret = EOK; + } + + abs2tm(&tm); + + *out = mktime(&tm); + DEBUG(3, ("converted to time: '%s'\n", ctime(out))); + if (*out == -1) { + ret = EINVAL; + } +done: + talloc_free(gctx); + return ret; +} + +/* + * Parses absolute timerange string given in STR and fills the + * information into ABS. + */ +static int parse_absolute(struct absolute_range *absr, + struct time_rules_ctx *trctx, + struct parse_ctx *pctx, + const char *str) +{ + char *from = NULL, *to = NULL; + int ret; + + ret = copy_substring(pctx, str, "from", &from); + if (ret != EOK) { + DEBUG(1, ("Missing required part 'from' in absolute timespec\n")); + ret = EINVAL; + goto done; + } + ret = copy_substring(pctx, str, "to", &to); + if (ret != EOK) { + DEBUG(1, ("Missing required part 'to' in absolute timespec\n")); + ret = EINVAL; + goto done; + } + + ret = parse_generalized_time(pctx, trctx, from, &absr->time_from); + if (ret != EOK) { + DEBUG(1, ("Cannot parse generalized time - first part\n")); + goto done; + } + + ret = parse_generalized_time(pctx, trctx, to, &absr->time_to); + if (ret != EOK) { + DEBUG(1, ("Cannot parse generalized time - second part\n")); + goto done; + } + + if (difftime(absr->time_to, absr->time_from) < 0) { + DEBUG(1, ("Not a valid interval\n")); + ret = EINVAL; + } + + ret = EOK; +done: + talloc_free(from); + talloc_free(to); + return ret; +} + +static int parse_hhmm(const char *str, int *hour, int *min) +{ + struct tm t; + char *err; + + err = strptime(str, "%H%M", &t); + if (*err != '\0') { + return EINVAL; + } + + *hour = t.tm_hour; + *min = t.tm_min; + + return EOK; +} + +/* + * Parses monthly periodic timerange given in STR. + * Fills the information into PER. + */ +static int parse_periodic_monthly(TALLOC_CTX *ctx, + struct time_rules_ctx *trctx, + struct periodic_range *per, + const char *str) +{ + int ret; + struct parse_ctx *mpctx = NULL; + char *match = NULL; + char *mperspec = NULL; + + /* This code would be much less ugly if RHEL5 PCRE knew about PCRE_DUPNAMES */ + ret = matches_regexp(ctx, trctx, str, LP_RGX_MDAY, &mpctx); + if (ret == EOK) { + ret = copy_substring(mpctx, str, "mperspec_day", &mperspec); + JMP_NEOK(ret); + ret = copy_substring(mpctx, str, "interval_day", &match); + JMP_NEOK(ret); + BUFFER_OR_JUMP(ctx, per->day_of_month, DAY_OF_MONTH_BUFSIZE); + ret = interval2bitfield(mpctx, per->day_of_month, match, + 1, DAY_OF_MONTH_MAX, NULL); + JMP_NEOK(ret); + } else { + ret = matches_regexp(ctx, trctx, str, LP_RGX_MWEEK, &mpctx); + JMP_NEOK(ret); + ret = copy_substring(mpctx, str, "mperspec_week", &mperspec); + JMP_NEOK(ret); + + ret = copy_substring(mpctx, str, "interval_week", &match); + JMP_NEOK(ret); + ret = interval2bitfield(mpctx, &per->week_of_month, match, + 1, WEEK_OF_MONTH_MAX, NULL); + JMP_NEOK(ret); + + ret = copy_substring(mpctx, str, "day_of_week", &match); + JMP_NEOK(ret); + ret = interval2bitfield(mpctx, &per->day_of_week, match, + 1, DAY_OF_WEEK_MAX, names_day_of_week); + JMP_NEOK(ret); + } + +done: + talloc_free(mpctx); + return ret; +} + +/* + * Parses yearly periodic timerange given in STR. + * Fills the information into PER. + */ +static int parse_periodic_yearly(TALLOC_CTX *ctx, + struct time_rules_ctx *trctx, + struct periodic_range *per, + const char *str) +{ + int ret; + struct parse_ctx *ypctx = NULL; + char *match = NULL; + char *yperspec = NULL; + + ret = matches_regexp(ctx, trctx, str, LP_RGX_YEARLY, &ypctx); + JMP_NEOK(ret); + ret = copy_substring(ypctx, str, "yperspec_day", &yperspec); + if (ret == EOK) { + ret = copy_substring(ypctx, str, "day_of_year", &match); + JMP_NEOK(ret); + BUFFER_OR_JUMP(ypctx, per->day_of_year, DAY_OF_YEAR_BUFSIZE); + ret = interval2bitfield(ypctx, per->day_of_year, match, + 1, DAY_OF_YEAR_MAX, NULL); + JMP_NEOK(ret); + } + + if (ret != ENOENT) goto done; + + ret = copy_substring(ypctx, str, "yperspec_week", &yperspec); + if (ret == EOK) { + ret = copy_substring(ypctx, str, "week_of_year", &match); + JMP_NEOK(ret); + BUFFER_OR_JUMP(ypctx, per->week_of_year, WEEK_OF_YEAR_BUFSIZE); + ret = interval2bitfield(ypctx, per->week_of_year, match, + 1, WEEK_OF_YEAR_MAX, NULL); + JMP_NEOK(ret); + + talloc_free(match); + ret = copy_substring(ypctx, str, "day_of_week", &match); + JMP_NEOK(ret); + ret = interval2bitfield(ypctx, &per->day_of_week, match, + 1, DAY_OF_WEEK_MAX, names_day_of_week); + JMP_NEOK(ret); + } + + if (ret != ENOENT) goto done; + + ret = copy_substring(ypctx, str, "yperspec_month", &yperspec); + JMP_NEOK(ret); + + talloc_free(match); + ret = copy_substring(ypctx, str, "month_number", &match); + JMP_NEOK(ret); + BUFFER_OR_JUMP(ypctx, per->month, MONTH_BUFSIZE); + ret = interval2bitfield(ypctx, per->month, match, + 1, MONTH_MAX, names_months); + JMP_NEOK(ret); + + talloc_free(match); + ret = copy_substring(ypctx, str, "m_period", &match); + JMP_NEOK(ret); + DEBUG(7, ("Monthly year period - calling parse_periodic_monthly()\n")); + ret = parse_periodic_monthly(ypctx, trctx, per, match); + JMP_NEOK(ret); + +done: + talloc_free(ypctx); + return ret; +} + +/* + * Parses weekly periodic timerange given in STR. + * Fills the information into PER. + */ +static int parse_periodic_weekly(TALLOC_CTX *ctx, + struct time_rules_ctx *trctx, + struct periodic_range *per, + const char *str) +{ + int ret; + struct parse_ctx *wpctx = NULL; + char *dow = NULL; + + ret = matches_regexp(ctx, trctx, str, LP_RGX_WEEKLY, &wpctx); + JMP_NEOK(ret); + + ret = copy_substring(wpctx, str, "day_of_week", &dow); + JMP_NEOK(ret); + DEBUG(8, ("day_of_week = '%s'\n", dow)); + + ret = interval2bitfield(wpctx, &per->day_of_week, dow, + 1, DAY_OF_WEEK_MAX, names_day_of_week); + +done: + talloc_free(wpctx); + return ret; +} + +static int parse_periodic_time(struct periodic_range *per, + struct parse_ctx *pctx, + const char *str) +{ + char *substr = NULL; + int ret; + + int hour_from; + int hour_to; + int min_from; + int min_to; + + /* parse out the time */ + ret = copy_substring(pctx, str, "timeFrom", &substr); + JMP_NEOK(ret); + parse_hhmm(substr, &hour_from, &min_from); + DEBUG(7, ("Parsed timeFrom: %d:%d\n", hour_from, min_from)); + JMP_NEOK(ret); + + talloc_free(substr); + ret = copy_substring(pctx, str, "timeTo", &substr); + JMP_NEOK(ret); + parse_hhmm(substr, &hour_to, &min_to); + DEBUG(7, ("Parsed timeTo: %d:%d\n", hour_to, min_to)); + JMP_NEOK(ret); + + /* set the interval */ + if (hour_from > hour_to ) { + set_bit_range(per->hour, 0, hour_to); + set_bit_range(per->hour, hour_from, HOUR_MAX); + } else { + set_bit_range(per->hour, hour_from, hour_to); + } + + if (min_from > min_to) { + set_bit_range(per->minute, 0, min_to); + set_bit_range(per->minute, min_from, MINUTE_MAX); + } else { + set_bit_range(per->minute, min_from, min_to); + } + + + ret = EOK; +done: + talloc_free(substr); + return ret; +} + +/* + * Parses periodic timerange given in STR. + * Fills the information into PER. + */ +static int parse_periodic(struct periodic_range *per, + struct time_rules_ctx *trctx, + struct parse_ctx *pctx, + const char *str) +{ + char *substr = NULL; + char *period = NULL; + int ret; + + /* These are mandatory */ + BUFFER_OR_JUMP(per, per->hour, HOUR_BUFSIZE); + BUFFER_OR_JUMP(per, per->minute, MINUTE_BUFSIZE); + + ret = copy_substring(pctx, str, "perspec", &substr); + JMP_NEOK(ret); + ret = copy_substring(pctx, str, "period", &period); + JMP_NEOK(ret); + + if (strcmp(substr, "yearly") == 0) { + DEBUG(5, ("periodic yearly\n")); + ret = parse_periodic_yearly(pctx, trctx, per, period); + JMP_NEOK(ret); + } else if (strcmp(substr, "monthly") == 0) { + DEBUG(5, ("periodic monthly\n")); + ret = parse_periodic_monthly(pctx, trctx, per, period); + JMP_NEOK(ret); + } else if (strcmp(substr, "weekly") == 0) { + DEBUG(5, ("periodic weekly\n")); + ret = parse_periodic_weekly(pctx, trctx, per, period); + JMP_NEOK(ret); + } else if (strcmp(substr, "daily") == 0) { + DEBUG(5, ("periodic daily\n")); + } else { + DEBUG(1, ("Cannot determine periodic rule type" + "(perspec = '%s', period = '%s')\n", substr, period)); + ret = EINVAL; + goto done; + } + + talloc_free(period); + + ret = parse_periodic_time(per, pctx, str); + JMP_NEOK(ret); + + ret = EOK; +done: + talloc_free(substr); + return ret; +} + +/* + * Parses time specification given in string RULE into range_ctx + * context CTX. + */ +static int parse_timespec(struct range_ctx *ctx, const char *rule) +{ + int ret; + struct parse_ctx *pctx = NULL; + + if (matches_regexp(ctx, ctx->trctx, rule, LP_RGX_ABSOLUTE, &pctx) == EOK) { + DEBUG(5, ("Matched absolute range\n")); + ctx->type = TYPE_ABSOLUTE; + ctx->abs = talloc_zero(ctx, struct absolute_range); + CHECK_PTR_JMP(ctx->abs); + + ret = parse_absolute(ctx->abs, ctx->trctx, pctx, rule); + JMP_NEOK(ret); + } else if (matches_regexp(ctx, ctx->trctx, rule, LP_RGX_PERIODIC, &pctx) == EOK) { + DEBUG(5, ("Matched periodic range\n")); + ctx->type = TYPE_PERIODIC; + ctx->per = talloc_zero(ctx, struct periodic_range); + CHECK_PTR_JMP(ctx->per); + + ret = parse_periodic(ctx->per, ctx->trctx, pctx, rule); + JMP_NEOK(ret); + } else { + DEBUG(1, ("Cannot determine rule type\n")); + ret = EINVAL; + goto done; + } + + ret = EOK; +done: + talloc_free(pctx); + return ret; +} + +/******************************************************************* + * validation of rules against time_t * + *******************************************************************/ + +static int absolute_timerange_valid(struct absolute_range *absr, + const time_t now, + bool *result) +{ + if (difftime(absr->time_from, now) > 0) { + DEBUG(3, ("Absolute timerange invalid (before interval)\n")); + *result = false; + return EOK; + } + + if (difftime(absr->time_to, now) < 0) { + DEBUG(3, ("Absolute timerange invalid (after interval)\n")); + *result = false; + return EOK; + } + + DEBUG(3, ("Absolute timerange valid\n")); + *result = true; + return EOK; +} + +static int periodic_timerange_valid(struct periodic_range *per, + const time_t now, + bool *result) +{ + struct tm tm_now; + int wnum; + int wom; + + memset(&tm_now, 0, sizeof(struct tm)); + if (localtime_r(&now, &tm_now) == NULL) { + DEBUG(0, ("Cannot convert time_t to struct tm\n")); + return EFAULT; + } + DEBUG(9, ("Got struct tm value %s", asctime(&tm_now))); + tm2abs(&tm_now); + + wnum = weeknum(&tm_now); + if (wnum == -1) { + DEBUG(7, ("Cannot get week number")); + return EINVAL; + } + DEBUG(9, ("Week number is %d\n", wnum)); + + wom = get_week_of_month(&tm_now); + if (wnum == -1) { + DEBUG(7, ("Cannot get week of number")); + return EINVAL; + } + DEBUG(9, ("Week of month number is %d\n", wom)); + + /* The validation itself */ + TEST_BIT_RANGE(per->day_of_week, tm_now.tm_wday, result); + DEBUG(9, ("day of week OK\n")); + TEST_BIT_RANGE_PTR(per->day_of_month, tm_now.tm_mday, result); + DEBUG(9, ("day of month OK\n")); + TEST_BIT_RANGE(per->week_of_month, wom, result); + DEBUG(9, ("week of month OK\n")); + TEST_BIT_RANGE_PTR(per->week_of_year, wnum, result); + DEBUG(9, ("week of year OK\n")); + TEST_BIT_RANGE_PTR(per->month, tm_now.tm_mon, result); + DEBUG(9, ("month OK\n")); + TEST_BIT_RANGE_PTR(per->day_of_year, tm_now.tm_yday, result); + DEBUG(9, ("day of year OK\n")); + TEST_BIT_RANGE_PTR(per->hour, tm_now.tm_hour, result); + DEBUG(9, ("hour OK\n")); + TEST_BIT_RANGE_PTR(per->minute, tm_now.tm_min, result); + DEBUG(9, ("minute OK\n")); + + DEBUG(3, ("Periodic timerange valid\n")); + *result = true; + return EOK; +} + +/* + * Returns EOK if the timerange in range_ctx context is valid compared against a + * given time_t value in NOW, returns ERANGE if the time value is outside the + * specified range. + */ +static int timerange_valid(struct range_ctx *ctx, + const time_t now, + bool *result) +{ + int ret; + + switch(ctx->type) { + case TYPE_ABSOLUTE: + DEBUG(7, ("Checking absolute range\n")); + ret = absolute_timerange_valid(ctx->abs, now, result); + break; + + case TYPE_PERIODIC: + DEBUG(7, ("Checking periodic range\n")); + ret = periodic_timerange_valid(ctx->per, now, result); + break; + + default: + DEBUG(1, ("Unknown range type (%d)\n", ctx->type)); + ret = EINVAL; + break; + } + + return ret; +} + +/******************************************************************* + * public interface * + *******************************************************************/ + +/* + * This is actually the meat of the library. The function takes a string + * representation of a time rule in STR and time to check against (usually that + * would be current time) in NOW. + * + * It returns EOK if the rule is valid in the current time, ERANGE if not and + * EINVAL if the rule cannot be parsed + */ +int check_time_rule(TALLOC_CTX *mem_ctx, + struct time_rules_ctx *trctx, + const char *str, + const time_t now, + bool *result) +{ + int ret; + struct range_ctx *ctx; + + ctx = talloc_zero(mem_ctx, struct range_ctx); + CHECK_PTR_JMP(ctx); + ctx->trctx = trctx; + + DEBUG(9, ("Got time_t value %s", ctime(&now))); + + ret = parse_timespec(ctx, str); + if (ret != EOK) { + DEBUG(1, ("Cannot parse the time specification (%d)\n", ret)); + goto done; + } + + ret = timerange_valid(ctx, now, result); + if (ret != EOK) { + DEBUG(1, ("Cannot check the time range (%d)\n", ret)); + goto done; + } + + ret = EOK; +done: + talloc_free(ctx); + return EOK; +} + +/* + * Frees the resources taken by the precompiled rules + */ +static int time_rules_parser_destructor(struct time_rules_ctx *ctx) +{ + int i; + + for (i = 0; i< LP_RGX_MAX; ++i) { + pcre_free(ctx->re[i]); + ctx->re[i] = NULL; + } + + return 0; +} + +/* + * Initializes the parser by precompiling the regular expressions + * for later use + */ +int init_time_rules_parser(TALLOC_CTX *mem_ctx, + struct time_rules_ctx **_out) +{ + const char *errstr; + int errval; + int errpos; + int ret; + int i; + struct time_rules_ctx *ctx = NULL; + + ctx = talloc_zero(mem_ctx, struct time_rules_ctx); + CHECK_PTR(ctx); + talloc_set_destructor(ctx, time_rules_parser_destructor); + + /* Precompile regular expressions */ + for (i = LP_RGX_GENERALIZED; i< LP_RGX_MAX; ++i) { + ctx->re[i] = pcre_compile2(lookup_table[i], + 0, + &errval, + &errstr, + &errpos, + NULL); + + if (ctx->re[i] == NULL) { + DEBUG(0, ("Invalid Regular Expression pattern '%s' at position %d" + " (Error: %d [%s])\n", lookup_table[i], + errpos, errval, errstr)); + ret = EFAULT; + goto done; + } + + } + + *_out = ctx; + return EOK; +done: + talloc_free(ctx); + return ret; +} + diff --git a/src/providers/ipa/ipa_timerules.h b/src/providers/ipa/ipa_timerules.h new file mode 100644 index 00000000..e1beaa22 --- /dev/null +++ b/src/providers/ipa/ipa_timerules.h @@ -0,0 +1,56 @@ +/* + SSSD + + IPA Provider Time Rules Parsing + + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) Red Hat, Inc 2009 + + 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/>. +*/ + +#ifndef __IPA_TIMERULES_H_ +#define __IPA_TIMERULES_H_ + +#include <stdbool.h> +#include <talloc.h> + +/* Opaque structure given after init */ +struct time_rules_ctx; + +/* + * Init the parser. Destroy the allocated resources by simply + * talloc_free()-ing the time_rules_ctx + */ +int init_time_rules_parser(TALLOC_CTX *mem_ctx, + struct time_rules_ctx **_out); + +/* + * This is actually the meat of the library. The function takes a string + * representation of a time rule in STR and time to check against (usually that + * would be current time) in NOW. + * + * It returns EOK if the rule can be parsed, error code if not. If the time + * given in the NOW parameter would be accepted by the rule, it stores true in + * RESULT, false otherwise. + */ +int check_time_rule(TALLOC_CTX *mem_ctx, + struct time_rules_ctx *trctx, + const char *str, + const time_t now, + bool *result); + +#endif /* __IPA_TIMERULES_H_ */ |