summaryrefslogtreecommitdiffstats
path: root/src/providers/ipa
diff options
context:
space:
mode:
Diffstat (limited to 'src/providers/ipa')
-rw-r--r--src/providers/ipa/ipa_access.c1823
-rw-r--r--src/providers/ipa/ipa_access.h66
-rw-r--r--src/providers/ipa/ipa_auth.c313
-rw-r--r--src/providers/ipa/ipa_auth.h32
-rw-r--r--src/providers/ipa/ipa_common.c597
-rw-r--r--src/providers/ipa/ipa_common.h83
-rw-r--r--src/providers/ipa/ipa_init.c293
-rw-r--r--src/providers/ipa/ipa_timerules.c1186
-rw-r--r--src/providers/ipa/ipa_timerules.h56
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 000000000..7dfe1fd90
--- /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 000000000..bd221c574
--- /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 000000000..86b72e495
--- /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 000000000..3079bbd1b
--- /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 000000000..7686227a5
--- /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 000000000..60c7313f0
--- /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 000000000..10b9257a2
--- /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 000000000..1a52eef12
--- /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 000000000..e1beaa22e
--- /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_ */