From 36a12aea020a935ffa40505fa02860c3d921ad0c Mon Sep 17 00:00:00 2001 From: Sumit Bose Date: Thu, 16 Feb 2012 09:53:53 -0500 Subject: Add s2n extended operation --- Makefile.am | 1 + src/providers/ipa/ipa_id.h | 10 + src/providers/ipa/ipa_s2n_exop.c | 657 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 668 insertions(+) create mode 100644 src/providers/ipa/ipa_s2n_exop.c diff --git a/Makefile.am b/Makefile.am index 00f466c6..fc54e716 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1196,6 +1196,7 @@ libsss_ipa_la_SOURCES = \ src/providers/ipa/ipa_dyndns.c \ src/providers/ipa/ipa_hosts.c \ src/providers/ipa/ipa_subdomains.c \ + src/providers/ipa/ipa_s2n_exop.c \ src/providers/ipa/ipa_hbac_hosts.c \ src/providers/ipa/ipa_hbac_private.h \ src/providers/ipa/ipa_hbac_rules.c \ diff --git a/src/providers/ipa/ipa_id.h b/src/providers/ipa/ipa_id.h index 3a8fdb44..b7923449 100644 --- a/src/providers/ipa/ipa_id.h +++ b/src/providers/ipa/ipa_id.h @@ -49,4 +49,14 @@ int ipa_get_netgroups_recv(struct tevent_req *req, void ipa_check_online(struct be_req *be_req); +struct tevent_req *ipa_s2n_get_acct_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sdap_handle *sh, + const char **attrs, + int entry_type, + const char *user_name, + uid_t uid); +int ipa_s2n_get_acct_info_recv(struct tevent_req *req); #endif diff --git a/src/providers/ipa/ipa_s2n_exop.c b/src/providers/ipa/ipa_s2n_exop.c new file mode 100644 index 00000000..26cb0aad --- /dev/null +++ b/src/providers/ipa/ipa_s2n_exop.c @@ -0,0 +1,657 @@ +/* + SSSD + + IPA Helper routines - external users and groups with s2n plugin + + Copyright (C) Sumit Bose - 2011 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "util/util.h" +#include "util/sss_nss.h" +#include "db/sysdb.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/ldap_common.h" + +enum input_types { + INP_SID = 1, + INP_NAME, + INP_POSIX_UID, + INP_POSIX_GID +}; + +enum request_types { + REQ_SIMPLE = 1, + REQ_FULL +}; + +enum response_types { + RESP_SID = 1, + RESP_NAME, + RESP_USER, + RESP_GROUP +}; + +/* ==Sid2Name Extended Operation============================================= */ +#define EXOP_SID2NAME_OID "2.16.840.1.113730.3.8.10.4" + +struct ipa_s2n_exop_state { + struct sdap_handle *sh; + + struct sdap_op *op; + + int result; + char *retoid; + struct berval *retdata; +}; + +static void ipa_s2n_exop_done(struct sdap_op *op, + struct sdap_msg *reply, + int error, void *pvt); + +static struct tevent_req *ipa_s2n_exop_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct berval *bv) +{ + struct tevent_req *req = NULL; + struct ipa_s2n_exop_state *state; + int ret; + int msgid; + + req = tevent_req_create(mem_ctx, &state, struct ipa_s2n_exop_state); + if (!req) return NULL; + + state->sh = sh; + state->result = LDAP_OPERATIONS_ERROR; + state->retoid = NULL; + state->retdata = NULL; + + DEBUG(SSSDBG_TRACE_FUNC, ("Executing extended operation\n")); + + ret = ldap_extended_operation(state->sh->ldap, EXOP_SID2NAME_OID, + bv, NULL, NULL, &msgid); + if (ret == -1 || msgid == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, ("ldap_extended_operation failed\n")); + goto fail; + } + DEBUG(SSSDBG_TRACE_INTERNAL, ("ldap_extended_operation sent, msgid = %d\n", msgid)); + + /* FIXME: get timeouts from configuration, for now 10 secs. */ + ret = sdap_op_add(state, ev, state->sh, msgid, ipa_s2n_exop_done, req, 10, + &state->op); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to set up operation!\n")); + goto fail; + } + + return req; + +fail: + tevent_req_error(req, EIO); + tevent_req_post(req, ev); + return req; +} + +static void ipa_s2n_exop_done(struct sdap_op *op, + struct sdap_msg *reply, + int error, void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct ipa_s2n_exop_state *state = tevent_req_data(req, + struct ipa_s2n_exop_state); + int ret; + char *errmsg = NULL; + char *retoid = NULL; + struct berval *retdata = NULL; + + if (error) { + tevent_req_error(req, error); + return; + } + + ret = ldap_parse_result(state->sh->ldap, reply->msg, + &state->result, &errmsg, NULL, NULL, + NULL, 0); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, ("ldap_parse_result failed (%d)\n", state->op->msgid)); + ret = EIO; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, ("ldap_extended_operation result: %s(%d), %s\n", + sss_ldap_err2string(state->result), state->result, errmsg)); + + if (state->result != LDAP_SUCCESS) { + ret = EIO; + goto done; + } + + ret = ldap_parse_extended_result(state->sh->ldap, reply->msg, + &retoid, &retdata, 0); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, ("ldap_parse_extendend_result failed (%d)\n", ret)); + ret = EIO; + goto done; + } + + state->retoid = talloc_strdup(state, retoid); + if (state->retoid == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("talloc_strdup failed.\n")); + ret = ENOMEM; + goto done; + } + + state->retdata = talloc(state, struct berval); + if (state->retdata == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("talloc failed.\n")); + ret = ENOMEM; + goto done; + } + state->retdata->bv_len = retdata->bv_len; + state->retdata->bv_val = talloc_memdup(state->retdata, retdata->bv_val, + retdata->bv_len); + if (state->retdata->bv_val == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("talloc_memdup failed.\n")); + ret = ENOMEM; + goto done; + } + + ret = EOK; + +done: + ldap_memfree(errmsg); + ldap_memfree(retoid); + ber_bvfree(retdata); + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +static int ipa_s2n_exop_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + enum sdap_result *result, char **retoid, + struct berval **retdata) +{ + struct ipa_s2n_exop_state *state = tevent_req_data(req, + struct ipa_s2n_exop_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (state->result == LDAP_SUCCESS) { + *result = SDAP_SUCCESS; + *retoid = talloc_steal(mem_ctx, state->retoid); + *retdata = talloc_steal(mem_ctx, state->retdata); + } else { + *result = SDAP_ERROR; + } + + return EOK; +} + +static errno_t talloc_ber_flatten(TALLOC_CTX *mem_ctx, BerElement *ber, + struct berval **_bv) +{ + int ret; + struct berval *bv = NULL; + struct berval *tbv = NULL; + + ret = ber_flatten(ber, &bv); + if (ret == -1) { + ret = EFAULT; + goto done; + } + + tbv = talloc_zero(mem_ctx, struct berval); + if (tbv == NULL) { + ret = ENOMEM; + goto done; + } + + tbv->bv_len = bv->bv_len; + tbv->bv_val = talloc_memdup(tbv, bv->bv_val, bv->bv_len); + if (tbv->bv_val == NULL) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + +done: + ber_bvfree(bv); + if (ret == EOK) { + *_bv = tbv; + } else { + talloc_free(tbv); + } + + return ret; +} + +/* The extended operation expect the following ASN.1 encoded request data: + * + * ExtdomRequestValue ::= SEQUENCE { + * inputType ENUMERATED { + * sid (1), + * name (2), + * posix uid (3), + * posix gid (3) + * }, + * requestType ENUMERATED { + * simple (1), + * full (2) + * }, + * data InputData + * } + * + * InputData ::= CHOICE { + * sid OCTET STRING, + * name NameDomainData + * uid PosixUid, + * gid PosixGid + * } + * + * NameDomainData ::= SEQUENCE { + * domain_name OCTET STRING, + * object_name OCTET STRING + * } + * + * PosixUid ::= SEQUENCE { + * domain_name OCTET STRING, + * uid INTEGER + * } + * + * PosixGid ::= SEQUENCE { + * domain_name OCTET STRING, + * gid INTEGER + * } + * + */ + +static errno_t s2n_encode_request(TALLOC_CTX *mem_ctx, + const char *domain_name, + int entry_type, + const char *name, + uint32_t id, + struct berval **_bv) +{ + BerElement *ber = NULL; + int ret; + + ber = ber_alloc_t( LBER_USE_DER ); + if (ber == NULL) { + return ENOMEM; + } + + switch (entry_type) { + case BE_REQ_USER: + if (name != NULL) { + ret = ber_printf(ber, "{ee{ss}}", INP_NAME, REQ_FULL, + domain_name, name); + } else { + ret = ber_printf(ber, "{ee{si}}", INP_POSIX_UID, REQ_FULL, + domain_name, id); + } + break; + case BE_REQ_GROUP: + if (name != NULL) { + ret = ber_printf(ber, "{ee{ss}}", INP_NAME, REQ_FULL, + domain_name, name); + } else { + ret = ber_printf(ber, "{ee{si}}", INP_POSIX_GID, REQ_FULL, + domain_name, id); + } + break; + default: + ret = EINVAL; + goto done; + } + if (ret == -1) { + ret = EFAULT; + goto done; + } + + ret = talloc_ber_flatten(mem_ctx, ber, _bv); + if (ret == -1) { + ret = EFAULT; + goto done; + } + + ret = EOK; + +done: + ber_free(ber, 1); + + return ret; +} + +/* If the extendend operation is successful it returns the following ASN.1 + * encoded response: + * + * ExtdomResponseValue ::= SEQUENCE { + * responseType ENUMERATED { + * sid (1), + * name (2), + * posix_user (3), + * posix_group (4) + * }, + * data OutputData + * } + * + * OutputData ::= CHOICE { + * sid OCTET STRING, + * name NameDomainData, + * user PosixUser, + * group PosixGroup + * } + * + * NameDomainData ::= SEQUENCE { + * domain_name OCTET STRING, + * object_name OCTET STRING + * } + * + * PosixUser ::= SEQUENCE { + * domain_name OCTET STRING, + * user_name OCTET STRING, + * uid INTEGER + * gid INTEGER + * } + * + * PosixGroup ::= SEQUENCE { + * domain_name OCTET STRING, + * group_name OCTET STRING, + * gid INTEGER + * } + * + * Since we always request the full data set (REQ_FULL), i.e user/group name, + * domain name and corresponding unix id, only PosixUser (RESP_USER) and + * PosixGroup (RESP_GROUP) are handled by s2n_response_to_attrs(). + */ + +struct resp_attrs { + enum response_types response_type; + char *domain_name; + union { + struct passwd user; + struct group group; + } a; +}; + +static errno_t s2n_response_to_attrs(TALLOC_CTX *mem_ctx, + char *retoid, + struct berval *retdata, + struct resp_attrs **resp_attrs) +{ + BerElement *ber = NULL; + ber_tag_t tag; + int ret; + enum response_types type; + char *domain_name = NULL; + char *name = NULL; + uid_t uid; + gid_t gid; + struct resp_attrs *attrs = NULL; + + if (retoid == NULL || retdata == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("Missing OID or data.\n")); + return EINVAL; + } + + if (strcmp(retoid, EXOP_SID2NAME_OID) != 0) { + DEBUG(SSSDBG_OP_FAILURE, + ("Result has wrong OID, expected [%s], got [%s].\n", + EXOP_SID2NAME_OID, retoid)); + return EINVAL; + } + + ber = ber_init(retdata); + if (ber == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("ber_init failed.\n")); + return EINVAL; + } + + tag = ber_scanf(ber, "{e", &type); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, ("ber_scanf failed.\n")); + ret = EINVAL; + goto done; + } + + attrs = talloc_zero(mem_ctx, struct resp_attrs); + if (attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("talloc_zero failed.\n")); + ret = ENOMEM; + goto done; + } + + switch (type) { + case RESP_USER: + tag = ber_scanf(ber, "{aaii}}", &domain_name, &name, &uid, &gid); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, ("ber_scanf failed.\n")); + ret = EINVAL; + goto done; + } + + attrs->a.user.pw_name = talloc_strdup(attrs, name); + if (attrs->a.user.pw_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("talloc_strdup failed.\n")); + ret = ENOMEM; + goto done; + } + + attrs->a.user.pw_uid = uid; + attrs->a.user.pw_gid = gid; + + break; + case RESP_GROUP: + tag = ber_scanf(ber, "{aai}}", &domain_name, &name, &gid); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, ("ber_scanf failed.\n")); + ret = EINVAL; + goto done; + } + + attrs->a.group.gr_name = talloc_strdup(attrs, name); + if (attrs->a.group.gr_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("talloc_strdup failed.\n")); + ret = ENOMEM; + goto done; + } + + attrs->a.group.gr_gid = gid; + + break; + default: + DEBUG(SSSDBG_OP_FAILURE, ("Unexpected response type [%d].\n", + type)); + ret = EINVAL; + goto done; + } + + attrs->response_type = type; + attrs->domain_name = talloc_strdup(attrs, domain_name); + if (attrs->domain_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("talloc_strdup failed.\n")); + ret = ENOMEM; + goto done; + } + + ret = EOK; + +done: + ber_memfree(domain_name); + ber_memfree(name); + ber_free(ber, 1); + + if (ret == EOK) { + *resp_attrs = attrs; + } else { + talloc_free(attrs); + } + + return ret; +} + +struct ipa_s2n_get_user_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sss_domain_info *dom; + struct sdap_handle *sh; + const char **expected_attrs; +}; + +static void ipa_s2n_get_user_done(struct tevent_req *subreq); + +struct tevent_req *ipa_s2n_get_acct_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sdap_handle *sh, + const char **attrs, + int entry_type, + const char *name, + uint32_t id) +{ + struct ipa_s2n_get_user_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + struct berval *bv_req = NULL; + int ret = EFAULT; + + if ((name == NULL && id == 0) || (name != NULL && id != 0)) { + DEBUG(SSSDBG_OP_FAILURE, ("Either a user name or a uid expected, " + "not both or nothing.\n")); + return NULL; + } + + req = tevent_req_create(mem_ctx, &state, struct ipa_s2n_get_user_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->opts = opts; + state->dom = dom; + state->sh = sh; + state->expected_attrs = attrs; + + ret = s2n_encode_request(state, dom->name, entry_type, name, id, &bv_req); + if (ret != EOK) { + goto fail; + } + + subreq = ipa_s2n_exop_send(state, state->ev, state->sh, bv_req); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("ipa_s2n_exop_send failed.\n")); + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, ipa_s2n_get_user_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + + return req; +} + +static void ipa_s2n_get_user_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_s2n_get_user_state *state = tevent_req_data(req, + struct ipa_s2n_get_user_state); + int ret; + enum sdap_result result; + char *retoid = NULL; + struct berval *retdata = NULL; + struct resp_attrs *attrs; + time_t now; + uint64_t timeout = 10*60*60; /* FIXME: find a better timeout ! */ + const char *homedir = NULL; + + ret = ipa_s2n_exop_recv(subreq, state, &result, &retoid, &retdata); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, ("s2n exop request failed.\n")); + goto done; + } + + ret = s2n_response_to_attrs(state, retoid, retdata, &attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, ("s2n_response_to_attrs failed.\n")); + goto done; + } + + if (strcasecmp(state->dom->name, attrs->domain_name) != 0) { + DEBUG(SSSDBG_OP_FAILURE, ("Unexpected domain name returned, " + "expected [%s], got [%s].\n", + state->dom->name, attrs->domain_name)); + ret = EINVAL; + goto done; + } + + now = time(NULL); + if (state->dom->subdomain_homedir) { + homedir = expand_homedir_template(state, state->dom->subdomain_homedir, + attrs->a.user.pw_name, + attrs->a.user.pw_uid, + state->dom->name); + if (homedir == NULL) { + ret = ENOMEM; + goto done; + } + } + + switch (attrs->response_type) { + case RESP_USER: + ret = sysdb_store_domuser(state->dom, attrs->a.user.pw_name, NULL, + attrs->a.user.pw_uid, + 0, NULL, /* gecos */ + homedir, NULL, + NULL, NULL, timeout, now); + break; + case RESP_GROUP: + ret = sysdb_store_domgroup(state->dom, attrs->a.group.gr_name, + attrs->a.group.gr_gid, NULL, timeout, + now); + break; + default: + DEBUG(SSSDBG_OP_FAILURE, ("Unexpected response type [%d].\n", + attrs->response_type)); + ret = EINVAL; + goto done; + } + + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + return; +} + +int ipa_s2n_get_acct_info_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} -- cgit