From 1dd679584241a0f9b29072c7eed1c5c5e4a577e4 Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Mon, 9 May 2016 16:36:36 +0200 Subject: [PATCH 14/19] Add initial providers infrastructure. Also adds support for the basic LOCAL provider that stores data on the local machine. Signed-off-by: Simo Sorce Reviewed-by: Jakub Hrozek --- Makefile.am | 11 + contrib/ci/deps.sh | 2 + contrib/sssd.spec.in | 2 + src/responder/secrets/local.c | 427 +++++++++++++++++++++++++++++++++ src/responder/secrets/providers.c | 360 +++++++++++++++++++++++++++ src/responder/secrets/secsrv_cmd.c | 183 +++++++++----- src/responder/secrets/secsrv_local.h | 28 +++ src/responder/secrets/secsrv_private.h | 121 ++++++++++ 8 files changed, 1074 insertions(+), 60 deletions(-) create mode 100644 src/responder/secrets/local.c create mode 100644 src/responder/secrets/providers.c create mode 100644 src/responder/secrets/secsrv_local.h create mode 100644 src/responder/secrets/secsrv_private.h diff --git a/Makefile.am b/Makefile.am index 139b280875a0a8424b80ec2c8246d4d206004698..a0a10009dedb19ff8d895e274259c6c7738336ff 100644 --- a/Makefile.am +++ b/Makefile.am @@ -88,6 +88,8 @@ polkitdir = @polkitdir@ pamconfdir = $(sysconfdir)/pam.d systemtap_tapdir = @tapset_dir@ +secdbpath = @secdbpath@ + UNICODE_LIBS=@UNICODE_LIBS@ MKDIR_P = @MKDIR_P@ @@ -95,6 +97,7 @@ INSTALL = @INSTALL@ SSSD_USER = @SSSD_USER@ + AM_CFLAGS = if WANT_AUX_INFO AM_CFLAGS += -aux-info $@.X @@ -622,6 +625,8 @@ dist_noinst_HEADERS = \ src/responder/ifp/ifp_groups.h \ src/responder/ifp/ifp_cache.h \ src/responder/secrets/secsrv.h \ + src/responder/secrets/secsrv_private.h \ + src/responder/secrets/secsrv_local.h \ src/sbus/sbus_client.h \ src/sbus/sssd_dbus.h \ src/sbus/sssd_dbus_meta.h \ @@ -1350,10 +1355,13 @@ if BUILD_SECRETS sssd_secrets_SOURCES = \ src/responder/secrets/secsrv.c \ src/responder/secrets/secsrv_cmd.c \ + src/responder/secrets/providers.c \ + src/responder/secrets/local.c \ $(SSSD_RESPONDER_OBJ) \ $(NULL) sssd_secrets_LDADD = \ $(HTTP_PARSER_LIBS) \ + $(JANSSON_LIBS) \ $(TDB_LIBS) \ $(SSSD_LIBS) \ $(SYSTEMD_DAEMON_LIBS) \ @@ -3925,6 +3933,9 @@ endif $(DESTDIR)$(pubconfpath)/krb5.include.d $(DESTDIR)$(gpocachepath) $(INSTALL) -d -m 0711 $(DESTDIR)$(sssdconfdir) \ $(DESTDIR)$(sssdconfdir)/conf.d +if BUILD_SECRETS + $(MKDIR_P) $(DESTDIR)$(secdbpath) +endif if HAVE_DOXYGEN docs: diff --git a/contrib/ci/deps.sh b/contrib/ci/deps.sh index ce2702c008f7145155ac5b0680a883ed16509860..1a94e3df2ee1d43dd34ef8cda1542aab1166bccd 100644 --- a/contrib/ci/deps.sh +++ b/contrib/ci/deps.sh @@ -117,6 +117,8 @@ if [[ "$DISTRO_BRANCH" == -debian-* ]]; then ldap-utils slapd systemtap-sdt-dev + libhttp-parser-dev + libjansson-dev ) DEPS_INTGCHECK_SATISFIED=true fi diff --git a/contrib/sssd.spec.in b/contrib/sssd.spec.in index 0d5715083a6155dfa2fb7d5356b876f9d40e35e4..b8a9efc07e2cb9325b5d445d546ec145391b95c2 100644 --- a/contrib/sssd.spec.in +++ b/contrib/sssd.spec.in @@ -172,6 +172,8 @@ BuildRequires: libsmbclient-devel %if (0%{?enable_systemtap} == 1) BuildRequires: systemtap-sdt-devel %endif +BuildRequires: http-parser-devel +BuildRequires: jansson-devel %description Provides a set of daemons to manage access to remote directories and diff --git a/src/responder/secrets/local.c b/src/responder/secrets/local.c new file mode 100644 index 0000000000000000000000000000000000000000..2887758398e06f6c88323612f8fe69f6808f52f7 --- /dev/null +++ b/src/responder/secrets/local.c @@ -0,0 +1,427 @@ +/* + SSSD + + Secrets Responder + + Copyright (C) Simo Sorce 2016 + + 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 "responder/secrets/secsrv_private.h" +#include + + + +int local_db_dn(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + const char *req_path, + struct ldb_dn **req_dn) +{ + struct ldb_dn *dn; + const char *s, *e; + int ret; + + dn = ldb_dn_new(mem_ctx, ldb, "cn=secrets"); + if (!dn) { + ret = ENOMEM; + goto done; + } + + s = req_path; + + while (s && *s) { + e = strchr(s, '/'); + if (e) { + if (e == s) { + s++; + continue; + } + if (!ldb_dn_add_child_fmt(dn, "cn=%.*s", (int)(e - s), s)) { + ret = ENOMEM; + goto done; + } + s = e + 1; + } else { + if (!ldb_dn_add_child_fmt(dn, "cn=%s", s)) { + ret = ENOMEM; + goto done; + } + s = NULL; + } + } + + *req_dn = dn; + ret = EOK; + +done: + return ret; +} + +char *local_dn_to_path(TALLOC_CTX *mem_ctx, + struct ldb_dn *basedn, + struct ldb_dn *dn) +{ + int basecomps; + int dncomps; + char *path = NULL; + + basecomps = ldb_dn_get_comp_num(basedn); + dncomps = ldb_dn_get_comp_num(dn); + + for (int i = dncomps - basecomps; i > 0; i--) { + const struct ldb_val *val; + + val = ldb_dn_get_component_val(dn, i - 1); + if (!val) return NULL; + + if (path) { + path = talloc_strdup_append_buffer(path, "/"); + if (!path) return NULL; + path = talloc_strndup_append_buffer(path, (char *)val->data, + val->length); + } else { + path = talloc_strndup(mem_ctx, (char *)val->data, val->length); + } + if (!path) return NULL; + } + + return path; +} + +int local_db_get_simple(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + const char *req_path, + char **secret) +{ + TALLOC_CTX *tmp_ctx; + static const char *attrs[] = { "secret", NULL }; + const char *filter = "(type=simple)"; + struct ldb_result *res; + struct ldb_dn *dn; + const char *attr_secret; + int ret; + + tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) return ENOMEM; + + ret = local_db_dn(tmp_ctx, ldb, req_path, &dn); + if (ret != EOK) goto done; + + ret = ldb_search(ldb, tmp_ctx, &res, dn, LDB_SCOPE_BASE, + attrs, "%s", filter); + if (ret != EOK) { + ret = ENOENT; + goto done; + } + + switch (res->count) { + case 0: + ret = ENOENT; + goto done; + case 1: + break; + default: + ret = E2BIG; + goto done; + } + + attr_secret = ldb_msg_find_attr_as_string(res->msgs[0], "secret", NULL); + if (!attr_secret) { + ret = ENOENT; + goto done; + } + + *secret = talloc_strdup(mem_ctx, attr_secret); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +int local_db_list_keys(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + const char *req_path, + char ***_keys, + int *num_keys) +{ + TALLOC_CTX *tmp_ctx; + static const char *attrs[] = { "secret", NULL }; + const char *filter = "(type=simple)"; + struct ldb_result *res; + struct ldb_dn *dn; + char **keys; + int ret; + + tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) return ENOMEM; + + ret = local_db_dn(tmp_ctx, ldb, req_path, &dn); + if (ret != EOK) goto done; + + ret = ldb_search(ldb, tmp_ctx, &res, dn, LDB_SCOPE_SUBTREE, + attrs, "%s", filter); + if (ret != EOK) { + ret = ENOENT; + goto done; + } + + if (res->count == 0) { + ret = ENOENT; + goto done; + } + + keys = talloc_array(mem_ctx, char *, res->count); + if (!keys) { + ret = ENOMEM; + goto done; + } + + for (int i = 0; i < res->count; i++) { + keys[i] = local_dn_to_path(keys, dn, res->msgs[i]->dn); + if (!keys[i]) { + ret = ENOMEM; + goto done; + } + } + + *_keys = keys; + *num_keys = res->count; + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +int local_db_put_simple(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + const char *req_path, + const char *secret) +{ + struct ldb_message *msg; + int ret; + + msg = ldb_msg_new(mem_ctx); + if (!msg) { + ret = ENOMEM; + goto done; + } + + ret = local_db_dn(msg, ldb, req_path, &msg->dn); + if (ret != EOK) goto done; + + ret = ldb_msg_add_string(msg, "type", "simple"); + if (ret != EOK) goto done; + + ret = ldb_msg_add_string(msg, "secret", secret); + if (ret != EOK) goto done; + + ret = ldb_add(ldb, msg); + if (ret != EOK) { + if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) ret = EEXIST; + else ret = EIO; + goto done; + } + + ret = EOK; + +done: + talloc_free(msg); + return ret; +} + +int local_db_delete(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + const char *req_path) +{ + struct ldb_dn *dn; + int ret; + + ret = local_db_dn(mem_ctx, ldb, req_path, &dn); + if (ret != EOK) goto done; + + ret = ldb_delete(ldb, dn); + if (ret != EOK) { + ret = EIO; + } + +done: + return ret; +} + +int local_secrets_map_path(TALLOC_CTX *mem_ctx, + struct sec_req_ctx *secreq, + char **local_db_path) +{ + int ret; + + /* be strict for now */ + if (secreq->parsed_url.fragment != NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unrecognized URI fragments: [%s]\n", + secreq->parsed_url.fragment); + return EINVAL; + } + + if (secreq->parsed_url.userinfo != NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unrecognized URI userinfo: [%s]\n", + secreq->parsed_url.userinfo); + return EINVAL; + } + + /* only type simple for now */ + if (secreq->parsed_url.query != NULL) { + ret = strcmp(secreq->parsed_url.query, "type=simple"); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Invalid URI query: [%s]\n", + secreq->parsed_url.query); + return EINVAL; + } + } + + /* drop SEC_BASEPATH prefix */ + *local_db_path = + talloc_strdup(mem_ctx, &secreq->mapped_path[sizeof(SEC_BASEPATH) - 1]); + if (!*local_db_path) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to map request to local db path\n"); + return ENOMEM; + } + + return EOK; +} + + +struct local_secret_state { + struct tevent_context *ev; + struct sec_req_ctx *secreq; +}; + +struct tevent_req *local_secret_req(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *provider_ctx, + struct sec_req_ctx *secreq) +{ + struct tevent_req *req; + struct local_secret_state *state; + struct ldb_context *ldb; + struct sec_data body = { 0 }; + char *req_path; + char *secret; + char **keys; + int nkeys; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct local_secret_state); + if (!req) return NULL; + + state->ev = ev; + state->secreq = secreq; + + ldb = talloc_get_type(provider_ctx, struct ldb_context); + if (!ldb) { + ret = EIO; + goto done; + } + + ret = local_secrets_map_path(state, secreq, &req_path); + if (ret) goto done; + + switch (secreq->method) { + case HTTP_GET: + if (req_path[strlen(req_path) - 1] == '/') { + ret = local_db_list_keys(state, ldb, req_path, &keys, &nkeys); + if (ret) goto done; + + ret = sec_array_to_json(state, keys, nkeys, &body.data); + if (ret) goto done; + } else { + ret = local_db_get_simple(state, ldb, req_path, &secret); + if (ret) goto done; + + ret = sec_simple_secret_to_json(state, secret, &body.data); + if (ret) goto done; + } + + body.length = strlen(body.data); + break; + + case HTTP_PUT: + ret = sec_json_to_simple_secret(state, secreq->body.data, &secret); + if (ret) goto done; + + ret = local_db_put_simple(state, ldb, req_path, secret); + if (ret) goto done; + break; + + case HTTP_DELETE: + ret = local_db_delete(state, ldb, req_path); + if (ret) goto done; + break; + + default: + ret = EINVAL; + goto done; + } + + if (body.data) { + ret = sec_http_reply_with_body(secreq, &secreq->reply, STATUS_200, + "application/json", &body); + } else { + ret = sec_http_status_reply(secreq, &secreq->reply, STATUS_200); + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + /* shortcircuit the request here as all called functions are + * synchronous and final and no further subrequests are made */ + tevent_req_done(req); + } + return tevent_req_post(req, state->ev); +} + +/* FIXME: allocate on the responder context */ +static struct provider_handle local_secrets_handle = { + .fn = local_secret_req, + .context = NULL, +}; + +int local_secrets_provider_handle(TALLOC_CTX *mem_ctx, + struct provider_handle **handle) +{ + struct ldb_context *ldb; + int ret; + + if (local_secrets_handle.context == NULL) { + ldb = ldb_init(NULL, NULL); + if (!ldb) return EIO; + + ret = ldb_connect(ldb, SECRETS_DB_PATH"/secrets.ldb", 0, NULL); + if (ret != LDB_SUCCESS) { + talloc_free(ldb); + return EIO; + } + + local_secrets_handle.context = ldb; + } + + *handle = &local_secrets_handle; + return EOK; +} diff --git a/src/responder/secrets/providers.c b/src/responder/secrets/providers.c new file mode 100644 index 0000000000000000000000000000000000000000..b641ebef8e3a170b44fa2dd33ad5f0b19fd96187 --- /dev/null +++ b/src/responder/secrets/providers.c @@ -0,0 +1,360 @@ +/* + SSSD + + Secrets Responder + + Copyright (C) Simo Sorce 2016 + + 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 "responder/secrets/secsrv_private.h" +#include "responder/secrets/secsrv_local.h" +#include + +int sec_map_url_to_user_path(struct sec_req_ctx *secreq, char **mapped_path) +{ + uid_t c_euid; + + c_euid = client_euid(secreq->cctx->creds); + + /* change path to be user specific */ + *mapped_path = + talloc_asprintf(secreq, SEC_BASEPATH"users/%"SPRIuid"/%s", + c_euid, + &secreq->parsed_url.path[sizeof(SEC_BASEPATH) - 1]); + if (!*mapped_path) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to map request to user specific url\n"); + return ENOMEM; + } + + return EOK; +} + +int sec_req_routing(TALLOC_CTX *mem_ctx, struct sec_req_ctx *secreq, + struct provider_handle **handle) +{ + char **sections; + char *def_provider; + char *provider; + const char *upath; + int ulen; + int num_sections; + int ret; + + /* patch must start with /secrets/ for now */ + ret = strncasecmp(secreq->parsed_url.path, + SEC_BASEPATH, sizeof(SEC_BASEPATH) - 1); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Path [%s] does not start with "SEC_BASEPATH"\n", + secreq->parsed_url.path); + return EPERM; + } + + ret = sec_map_url_to_user_path(secreq, &secreq->mapped_path); + if (ret) return ret; + + /* source default provider */ + ret = confdb_get_string(secreq->cctx->rctx->cdb, mem_ctx, + CONFDB_SEC_CONF_ENTRY, "provider", "LOCAL", + &def_provider); + if (ret) return EIO; + + ret = confdb_get_sub_sections(mem_ctx, secreq->cctx->rctx->cdb, + CONFDB_SEC_CONF_ENTRY, §ions, + &num_sections); + if (ret != EOK) return ret; + + provider = def_provider; + upath = &secreq->parsed_url.path[sizeof(SEC_BASEPATH) - 1]; + ulen = strlen(upath); + + // TODO order by length ? + for (int i = 0; ulen > 0 && i < num_sections; i++) { + if (strncmp(sections[i], upath, ulen) == 0) { + char *secname; + + secname = talloc_asprintf(mem_ctx, CONFDB_SEC_CONF_ENTRY"/%s", + sections[i]); + if (!secname) return ENOMEM; + + provider = NULL; + ret = confdb_get_string(secreq->cctx->rctx->cdb, mem_ctx, + secname, "provider", def_provider, + &provider); + if (ret || !provider) return EIO; + + secreq->cfg_section = talloc_steal(secreq, secname); + break; + } + } + + if (strcasecmp(provider, "LOCAL") == 0) { + return local_secrets_provider_handle(mem_ctx, handle); + } else { + DEBUG(SSSDBG_FATAL_FAILURE, + "Unknown provider type: %s\n", provider); + return EIO; + } + + return EINVAL; +} + +int sec_provider_recv(struct tevent_req *req) { + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +static struct sec_http_status_format_table { + int status; + const char *text; + const char *description; +} sec_http_status_format_table[] = { + { 200, "OK", "Success" }, + { 400, "Bad Request", + "The request format is invalid." }, + { 401, "Unauthorized", + "Access to the requested resource requires authentication." }, + { 403, "Forbidden", + "Access to the requested resource is forbidden." }, + { 404, "Not Found", + "The requested resource was not found." }, + { 405, "Method Not Allowed", + "Request method not allowed for this resource." }, + { 406, "Not Acceptable", + "The request cannot be accepted." }, + { 409, "Conflict", + "The requested resource already exists." }, + { 500, "Internal Server Error", + "The server encountered an internal error." }, +}; + +int sec_http_status_reply(TALLOC_CTX *mem_ctx, struct sec_data *reply, + enum sec_http_status_codes code) +{ + char *body = talloc_asprintf(mem_ctx, + "\r\n" + "\r\n%d %s\r\n" + "\r\n" + "

%s

\r\n" + "

%s

\r\n" + "", + sec_http_status_format_table[code].status, + sec_http_status_format_table[code].text, + sec_http_status_format_table[code].text, + sec_http_status_format_table[code].description); + if (!body) return ENOMEM; + + reply->data = talloc_asprintf(mem_ctx, + "HTTP/1.1 %d %s\r\n" + "Content-Length: %u\r\n" + "Content-Type: text/html\r\n" + "\r\n" + "%s", + sec_http_status_format_table[code].status, + sec_http_status_format_table[code].text, + (unsigned)strlen(body), body); + talloc_free(body); + if (!reply->data) return ENOMEM; + + reply->length = strlen(reply->data); + + return EOK; +} + +int sec_http_reply_with_body(TALLOC_CTX *mem_ctx, struct sec_data *reply, + enum sec_http_status_codes code, + const char *content_type, + struct sec_data *body) +{ + int head_size; + + reply->data = talloc_asprintf(mem_ctx, + "HTTP/1.1 %d %s\r\n" + "Content-Type: %s\r\n" + "Content-Length: %lu\r\n" + "\r\n", + sec_http_status_format_table[code].status, + sec_http_status_format_table[code].text, + content_type, body->length); + if (!reply->data) return ENOMEM; + + head_size = strlen(reply->data); + + reply->data = talloc_realloc(mem_ctx, reply->data, char, + head_size + body->length); + if (!reply->data) return ENOMEM; + + memcpy(&reply->data[head_size], body->data, body->length); + reply->length = head_size + body->length; + + return EOK; +} + +enum sec_http_status_codes sec_errno_to_http_status(errno_t err) +{ + switch (err) { + case EOK: + return STATUS_200; + case EINVAL: + return STATUS_400; + case EACCES: + return STATUS_401; + case EPERM: + return STATUS_403; + case ENOENT: + return STATUS_404; + case EISDIR: + return STATUS_405; + case EMEDIUMTYPE: + return STATUS_406; + case EEXIST: + return STATUS_409; + default: + return STATUS_500; + } +} + +int sec_json_to_simple_secret(TALLOC_CTX *mem_ctx, + const char *input, + char **secret) +{ + json_t *root; + json_t *element; + json_error_t error; + int ret; + + root = json_loads(input, 0, &error); + if (!root) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to parse JSON payload on line %d: %s\n", + error.line, error.text); + return EINVAL; + } + + if (!json_is_object(root)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Json data is not an object.\n"); + ret = EINVAL; + goto done; + } + + element = json_object_get(root, "type"); + if (!element) { + DEBUG(SSSDBG_CRIT_FAILURE, "Json data key 'type' not found.\n"); + ret = EINVAL; + goto done; + } + if (!json_is_string(element)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Json object 'type' is not a string.\n"); + ret = EINVAL; + goto done; + } + if (strcmp(json_string_value(element), "simple") != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Token type is not 'simple'.\n"); + ret = EMEDIUMTYPE; + goto done; + } + + element = json_object_get(root, "value"); + if (!element) { + DEBUG(SSSDBG_CRIT_FAILURE, "Json key 'value' not found.\n"); + ret = EINVAL; + goto done; + } + if (!json_is_string(element)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Json object 'value' is not a string.\n"); + ret = EINVAL; + goto done; + } + + *secret = talloc_strdup(mem_ctx, json_string_value(element)); + if (!*secret) { + ret = ENOMEM; + } else { + ret = EOK; + } + +done: + json_decref(root); + return ret; +} + +int sec_simple_secret_to_json(TALLOC_CTX *mem_ctx, + const char *secret, + char **output) +{ + char *jsonized = NULL; + json_t *root; + int ret; + + root = json_pack("{s:s, s:s}", "type", "simple", "value", secret); + if (!root) return ENOMEM; + + jsonized = json_dumps(root, JSON_INDENT(4)); + if (!jsonized) { + ret = ENOMEM; + goto done; + } + + *output = talloc_strdup(mem_ctx, jsonized); + if (!*output) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + +done: + json_decref(root); + free(jsonized); + return ret; +} + +int sec_array_to_json(TALLOC_CTX *mem_ctx, + char **array, int count, + char **output) +{ + char *jsonized = NULL; + json_t *root; + int ret; + + root = json_array(); + + for (int i = 0; i < count; i++) { + // FIXME: json_string mem leak ? + // FIXME: Error checking + json_array_append_new(root, json_string(array[i])); + } + + jsonized = json_dumps(root, JSON_INDENT(4)); + if (!jsonized) { + ret = ENOMEM; + goto done; + } + + *output = talloc_strdup(mem_ctx, jsonized); + if (!*output) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + +done: + json_decref(root); + free(jsonized); + return ret; +} diff --git a/src/responder/secrets/secsrv_cmd.c b/src/responder/secrets/secsrv_cmd.c index e9661001705e9dc312e2bd244cf8ca45539d07e0..00f0600d0b110a62821563e7aa34c985babbe886 100644 --- a/src/responder/secrets/secsrv_cmd.c +++ b/src/responder/secrets/secsrv_cmd.c @@ -19,79 +19,33 @@ along with this program. If not, see . */ +#include "config.h" #include "util/util.h" #include "responder/common/responder.h" #include "responder/secrets/secsrv.h" -#include "confdb/confdb.h" -#include +#include "responder/secrets/secsrv_private.h" #define SEC_REQUEST_MAX_SIZE 65536 #define SEC_PACKET_MAX_RECV_SIZE 8192 -struct sec_kvp { - char *name; - char *value; -}; - -struct sec_data { - char *data; - size_t length; -}; - -struct sec_proto_ctx { - http_parser_settings callbacks; - http_parser parser; -}; - -struct sec_req_ctx { - struct cli_ctx *cctx; - bool complete; - - size_t total_size; - - char *request_url; - struct sec_kvp *headers; - int num_headers; - struct sec_data body; - - struct sec_data reply; -}; - - /* ##### Request Handling ##### */ -static int error_403_reply(TALLOC_CTX *mem_ctx, - struct sec_data *reply) -{ - reply->data = talloc_asprintf(mem_ctx, - "HTTP/1.1 403 Forbidden\r\n" - "Content-Length: 0\r\n" - "\r\n" - "\r\n" - "\r\n\r\n403 Forbidden\r\n" - "\r\n

Forbidden

\r\n" - "

You don't have permission to access the requested " - "resource.

\r\n
\r\n"); - if (!reply->data) return ENOMEM; - - reply->length = strlen(reply->data); - - return EOK; -} - struct sec_http_request_state { struct tevent_context *ev; struct sec_req_ctx *secreq; }; +static void sec_http_request_pipeline_done(struct tevent_req *subreq); static struct tevent_req *sec_http_request_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sec_req_ctx *secreq) { struct tevent_req *req; + struct tevent_req *subreq; struct sec_http_request_state *state; - int ret; + struct provider_handle *provider_handle; + errno_t ret; req = tevent_req_create(mem_ctx, &state, struct sec_http_request_state); if (!req) return NULL; @@ -99,21 +53,47 @@ static struct tevent_req *sec_http_request_send(TALLOC_CTX *mem_ctx, state->ev = ev; state->secreq = secreq; - /* Take it form here */ + /* Go through the pipeline */ - /* For now, always return an error */ - ret = error_403_reply(state, &state->secreq->reply); - if (ret != EOK) goto done; + /* 1. mapping and path conversion */ + ret = sec_req_routing(state, secreq, &provider_handle); + if (ret) goto done; + + /* 2. backend invocation */ + subreq = provider_handle->fn(state, state->ev, + provider_handle->context, secreq); + if (!subreq) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sec_http_request_pipeline_done, req); + return req; done: if (ret != EOK) { tevent_req_error(req, ret); - } else { - tevent_req_done(req); } return tevent_req_post(req, state->ev); } +static void sec_http_request_pipeline_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + /* 3. reply construction */ + ret = sec_provider_recv(subreq); + + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } +} + static int sec_http_request_recv(struct tevent_req *req) { TEVENT_REQ_RETURN_ON_ERROR(req); @@ -126,10 +106,21 @@ static int sec_http_request_recv(struct tevent_req *req) static void sec_http_request_done(struct tevent_req *req) { - struct cli_ctx *cctx = tevent_req_callback_data(req, struct cli_ctx); + struct sec_req_ctx *secreq; + struct cli_ctx *cctx; int ret; + secreq = tevent_req_callback_data(req, struct sec_req_ctx); + cctx = secreq->cctx; + ret = sec_http_request_recv(req); + + if (ret != EOK) { + /* Always return an error if we get here */ + ret = sec_http_status_reply(secreq, &secreq->reply, + sec_errno_to_http_status(ret)); + } + if (ret != EOK) { DEBUG(SSSDBG_FATAL_FAILURE, "Failed to find reply, aborting client!\n"); @@ -155,7 +146,7 @@ static void sec_cmd_execute(struct cli_ctx *cctx) talloc_free(cctx); return; } - tevent_req_set_callback(req, sec_http_request_done, cctx); + tevent_req_set_callback(req, sec_http_request_done, secreq); } @@ -300,10 +291,82 @@ static int sec_on_body(http_parser *parser, return 0; } +static int sec_get_parsed_filed(TALLOC_CTX *mem_ctx, int field, + struct http_parser_url *parsed, + char *source_buf, + char **dest) +{ + uint16_t off = parsed->field_data[field].off; + uint16_t len = parsed->field_data[field].len; + *dest = talloc_strndup(mem_ctx, &source_buf[off], len); + if (!*dest) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to parse url, aborting client!\n"); + return ENOMEM; + } + return EOK; +} + static int sec_on_message_complete(http_parser *parser) { struct sec_req_ctx *req = talloc_get_type(parser->data, struct sec_req_ctx); + struct http_parser_url parsed; + int ret; + + /* parse url as well */ + ret = http_parser_parse_url(req->request_url, + strlen(req->request_url), + 0, &parsed); + if (ret) return ret; + + if (parsed.field_set & (1 << UF_SCHEMA)) { + ret = sec_get_parsed_filed(req, UF_SCHEMA, &parsed, + req->request_url, + &req->parsed_url.schema); + if (ret) return -1; + } + + if (parsed.field_set & (1 << UF_HOST)) { + ret = sec_get_parsed_filed(req, UF_HOST, &parsed, + req->request_url, + &req->parsed_url.host); + if (ret) return -1; + } + + if (parsed.field_set & (1 << UF_PORT)) { + req->parsed_url.port = parsed.port; + } + + if (parsed.field_set & (1 << UF_PATH)) { + ret = sec_get_parsed_filed(req, UF_PATH, &parsed, + req->request_url, + &req->parsed_url.path); + if (ret) return -1; + } + + if (parsed.field_set & (1 << UF_QUERY)) { + ret = sec_get_parsed_filed(req, UF_QUERY, &parsed, + req->request_url, + &req->parsed_url.query); + if (ret) return -1; + } + + if (parsed.field_set & (1 << UF_FRAGMENT)) { + ret = sec_get_parsed_filed(req, UF_FRAGMENT, &parsed, + req->request_url, + &req->parsed_url.fragment); + if (ret) return -1; + } + + if (parsed.field_set & (1 << UF_USERINFO)) { + ret = sec_get_parsed_filed(req, UF_USERINFO, &parsed, + req->request_url, + &req->parsed_url.userinfo); + if (ret) return -1; + } + + req->method = parser->method; req->complete = true; diff --git a/src/responder/secrets/secsrv_local.h b/src/responder/secrets/secsrv_local.h new file mode 100644 index 0000000000000000000000000000000000000000..537f61e53d098deecbc2530e9e3c21a07eaecada --- /dev/null +++ b/src/responder/secrets/secsrv_local.h @@ -0,0 +1,28 @@ +/* + SSSD + + Secrets Local Provider + + Copyright (C) Simo Sorce 2016 + + 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 . +*/ + +#ifndef __SECSRV_LOCAL_H__ +#define __SECSRV_LOCAL_H__ + +int local_secrets_provider_handle(TALLOC_CTX *mem_ctx, + struct provider_handle **handle); + +#endif /* __SECSRV_LOCAL_H__ */ diff --git a/src/responder/secrets/secsrv_private.h b/src/responder/secrets/secsrv_private.h new file mode 100644 index 0000000000000000000000000000000000000000..4b55f645a140e7650750196f7706ba183765efb3 --- /dev/null +++ b/src/responder/secrets/secsrv_private.h @@ -0,0 +1,121 @@ +/* + SSSD + + Secrets Responder, private header file + + Copyright (C) Simo Sorce 2016 + + 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 . +*/ + +#ifndef __SECSRV_PRIVATE_H__ +#define __SECSRV_PRIVATE_H__ + +#include "config.h" +#include "responder/common/responder.h" +#include + +struct sec_kvp { + char *name; + char *value; +}; + +struct sec_data { + char *data; + size_t length; +}; + +enum sec_http_status_codes { + STATUS_200 = 0, + STATUS_400, + STATUS_401, + STATUS_403, + STATUS_404, + STATUS_405, + STATUS_406, + STATUS_409, + STATUS_500, +}; + +struct sec_proto_ctx { + http_parser_settings callbacks; + http_parser parser; +}; + +struct sec_url { + char *schema; + char *host; + int port; + char *path; + char *fragment; + char *userinfo; + char *query; +}; + +struct sec_req_ctx { + struct cli_ctx *cctx; + const char *cfg_section; + bool complete; + + size_t total_size; + + char *request_url; + char *mapped_path; + + uint8_t method; + struct sec_url parsed_url; + struct sec_kvp *headers; + int num_headers; + struct sec_data body; + + struct sec_data reply; +}; + +typedef struct tevent_req *(*sec_provider_req_t)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *provider_ctx, + struct sec_req_ctx *secreq); + +struct provider_handle { + sec_provider_req_t fn; + void *context; +}; + +#define SEC_BASEPATH "/secrets/" + +/* providers.c */ +int sec_req_routing(TALLOC_CTX *mem_ctx, struct sec_req_ctx *secreq, + struct provider_handle **handle); +int sec_provider_recv(struct tevent_req *subreq); + +int sec_http_status_reply(TALLOC_CTX *mem_ctx, struct sec_data *reply, + enum sec_http_status_codes code); +int sec_http_reply_with_body(TALLOC_CTX *mem_ctx, struct sec_data *reply, + enum sec_http_status_codes code, + const char *content_type, + struct sec_data *body); +enum sec_http_status_codes sec_errno_to_http_status(errno_t err); + +int sec_json_to_simple_secret(TALLOC_CTX *mem_ctx, + const char *input, + char **secret); +int sec_simple_secret_to_json(TALLOC_CTX *mem_ctx, + const char *secret, + char **output); + +int sec_array_to_json(TALLOC_CTX *mem_ctx, + char **array, int count, + char **output); + +#endif /* __SECSRV_PRIVATE_H__ */ -- 2.4.11