From 8f2a34cc6964a1f80a1434e05315a7ae0bb5774e Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Mon, 9 May 2016 18:31:31 +0200 Subject: [PATCH 16/19] Secrets: Add Proxy backend Signed-off-by: Simo Sorce Reviewed-by: Jakub Hrozek --- Makefile.am | 5 + src/responder/secrets/local.c | 77 +- src/responder/secrets/providers.c | 129 ++- src/responder/secrets/proxy.c | 977 +++++++++++++++++++++ src/responder/secrets/secsrv.c | 7 + src/responder/secrets/secsrv.h | 7 + src/responder/secrets/secsrv_cmd.c | 8 +- src/responder/secrets/secsrv_local.h | 4 +- src/responder/secrets/secsrv_private.h | 24 +- .../secrets/{secsrv_local.h => secsrv_proxy.h} | 10 +- 10 files changed, 1182 insertions(+), 66 deletions(-) create mode 100644 src/responder/secrets/proxy.c copy src/responder/secrets/{secsrv_local.h => secsrv_proxy.h} (77%) diff --git a/Makefile.am b/Makefile.am index c6cae36a4b583b8a9546a1500ac1a0ffd92ead74..6224347134635c9bf1a0e7f54a6612d34693430a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -627,6 +627,7 @@ dist_noinst_HEADERS = \ src/responder/secrets/secsrv.h \ src/responder/secrets/secsrv_private.h \ src/responder/secrets/secsrv_local.h \ + src/responder/secrets/secsrv_proxy.h \ src/sbus/sbus_client.h \ src/sbus/sssd_dbus.h \ src/sbus/sssd_dbus_meta.h \ @@ -1364,7 +1365,10 @@ sssd_secrets_SOURCES = \ src/responder/secrets/secsrv_cmd.c \ src/responder/secrets/providers.c \ src/responder/secrets/local.c \ + src/responder/secrets/proxy.c \ + src/util/sss_sockets.c \ $(SSSD_RESPONDER_OBJ) \ + $(SSSD_RESOLV_OBJ) \ $(NULL) sssd_secrets_LDADD = \ $(HTTP_PARSER_LIBS) \ @@ -1372,6 +1376,7 @@ sssd_secrets_LDADD = \ $(TDB_LIBS) \ $(SSSD_LIBS) \ $(SYSTEMD_DAEMON_LIBS) \ + $(CARES_LIBS) \ $(SSSD_INTERNAL_LTLIBS) \ $(NULL) endif diff --git a/src/responder/secrets/local.c b/src/responder/secrets/local.c index 5c99c187705f1c3e3ff1592239d4e5dd5b5c85c8..6451ef95bf7e6b81936d492349a548b329748b22 100644 --- a/src/responder/secrets/local.c +++ b/src/responder/secrets/local.c @@ -506,57 +506,56 @@ int generate_master_key(const char *filename, size_t size) return EOK; } -/* 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) +int local_secrets_provider_handle(struct sec_ctx *sctx, + struct provider_handle **out_handle) { + const char *mkey = SECRETS_DB_PATH"/.secrets.mkey"; + const char *dbpath = SECRETS_DB_PATH"/secrets.ldb"; + struct provider_handle *handle; struct local_context *lctx; + ssize_t size; + int mfd; int ret; - if (local_secrets_handle.context == NULL) { - const char *mkey = SECRETS_DB_PATH"/.secrets.mkey"; - ssize_t size; - int mfd; + handle = talloc_zero(sctx, struct provider_handle); + if (!handle) return ENOMEM; - lctx = talloc_zero(NULL, struct local_context); - if (!lctx) return ENOMEM; + handle->name = "LOCAL"; + handle->fn = local_secret_req; - lctx->ldb = ldb_init(lctx, NULL); - if (!lctx->ldb) return ENOMEM; + lctx = talloc_zero(handle, struct local_context); + if (!lctx) return ENOMEM; - ret = ldb_connect(lctx->ldb, SECRETS_DB_PATH"/secrets.ldb", 0, NULL); - if (ret != LDB_SUCCESS) { - talloc_free(lctx->ldb); - return EIO; - } + lctx->ldb = ldb_init(lctx, NULL); + if (!lctx->ldb) return ENOMEM; - lctx->master_key.data = talloc_size(lctx, MKEY_SIZE); - if (!lctx->master_key.data) return ENOMEM; - lctx->master_key.length = MKEY_SIZE; + ret = ldb_connect(lctx->ldb, dbpath, 0, NULL); + if (ret != LDB_SUCCESS) { + talloc_free(lctx->ldb); + return EIO; + } + lctx->master_key.data = talloc_size(lctx, MKEY_SIZE); + if (!lctx->master_key.data) return ENOMEM; + lctx->master_key.length = MKEY_SIZE; + + ret = check_and_open_readonly(mkey, &mfd, 0, 0, + S_IFREG|S_IRUSR|S_IWUSR, 0); + if (ret == ENOENT) { + ret = generate_master_key(mkey, MKEY_SIZE); + if (ret) return EFAULT; ret = check_and_open_readonly(mkey, &mfd, 0, 0, S_IFREG|S_IRUSR|S_IWUSR, 0); - if (ret == ENOENT) { - ret = generate_master_key(mkey, MKEY_SIZE); - if (ret) return EFAULT; - ret = check_and_open_readonly(mkey, &mfd, 0, 0, - S_IFREG|S_IRUSR|S_IWUSR, 0); - } - if (ret) return EFAULT; - - size = sss_atomic_io_s(mfd, lctx->master_key.data, - lctx->master_key.length, true); - close(mfd); - if (size < 0 || size != lctx->master_key.length) return EIO; - - local_secrets_handle.context = lctx; } + if (ret) return EFAULT; - *handle = &local_secrets_handle; + size = sss_atomic_io_s(mfd, lctx->master_key.data, + lctx->master_key.length, true); + close(mfd); + if (size < 0 || size != lctx->master_key.length) return EIO; + + handle->context = lctx; + + *out_handle = handle; return EOK; } diff --git a/src/responder/secrets/providers.c b/src/responder/secrets/providers.c index b641ebef8e3a170b44fa2dd33ad5f0b19fd96187..5dff01a7fb966261f25f70dfabf71490a7b2b352 100644 --- a/src/responder/secrets/providers.c +++ b/src/responder/secrets/providers.c @@ -21,6 +21,7 @@ #include "responder/secrets/secsrv_private.h" #include "responder/secrets/secsrv_local.h" +#include "responder/secrets/secsrv_proxy.h" #include int sec_map_url_to_user_path(struct sec_req_ctx *secreq, char **mapped_path) @@ -46,14 +47,15 @@ int sec_map_url_to_user_path(struct sec_req_ctx *secreq, char **mapped_path) int sec_req_routing(TALLOC_CTX *mem_ctx, struct sec_req_ctx *secreq, struct provider_handle **handle) { + struct sec_ctx *sctx; char **sections; char *def_provider; char *provider; - const char *upath; - int ulen; int num_sections; int ret; + sctx = talloc_get_type(secreq->cctx->rctx->pvt_ctx, struct sec_ctx); + /* patch must start with /secrets/ for now */ ret = strncasecmp(secreq->parsed_url.path, SEC_BASEPATH, sizeof(SEC_BASEPATH) - 1); @@ -79,12 +81,16 @@ int sec_req_routing(TALLOC_CTX *mem_ctx, struct sec_req_ctx *secreq, 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) { + for (int i = 0; i < num_sections; i++) { + int slen; + + secreq->base_path = talloc_asprintf(secreq, SEC_BASEPATH"%s/", sections[i]); + if (!secreq->base_path) return ENOMEM; + slen = strlen(secreq->base_path); + + if (strncmp(secreq->base_path, secreq->mapped_path, slen) == 0) { char *secname; secname = talloc_asprintf(mem_ctx, CONFDB_SEC_CONF_ENTRY"/%s", @@ -98,19 +104,31 @@ int sec_req_routing(TALLOC_CTX *mem_ctx, struct sec_req_ctx *secreq, if (ret || !provider) return EIO; secreq->cfg_section = talloc_steal(secreq, secname); + if (!secreq->cfg_section) return ENOMEM; break; } + talloc_zfree(secreq->base_path); } - 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; + if (!secreq->base_path) secreq->base_path = SEC_BASEPATH; + + ret = sec_get_provider(sctx, provider, handle); + if (ret == ENOENT) { + if (strcasecmp(provider, "LOCAL") == 0) { + ret = local_secrets_provider_handle(sctx, handle); + } else if (strcasecmp(provider, "PROXY") == 0) { + ret = proxy_secrets_provider_handle(sctx, handle); + } else { + DEBUG(SSSDBG_FATAL_FAILURE, + "Unknown provider type: %s\n", provider); + ret = EIO; + } + if (ret == EOK) { + ret = sec_add_provider(sctx, *handle); + } } - return EINVAL; + return ret; } int sec_provider_recv(struct tevent_req *req) { @@ -204,6 +222,58 @@ int sec_http_reply_with_body(TALLOC_CTX *mem_ctx, struct sec_data *reply, return EOK; } +int sec_http_append_header(TALLOC_CTX *mem_ctx, char **dest, + char *field, char *value) +{ + if (*dest == NULL) { + *dest = talloc_asprintf(mem_ctx, "%s: %s\r\n", field, value); + } else { + *dest = talloc_asprintf_append_buffer(*dest, "%s: %s\r\n", + field, value); + } + if (!*dest) return ENOMEM; + + return EOK; +} + +int sec_http_reply_with_headers(TALLOC_CTX *mem_ctx, struct sec_data *reply, + int status_code, const char *reason, + struct sec_kvp *headers, int num_headers, + struct sec_data *body) +{ + const char *reason_phrase = reason ? reason : ""; + int ret; + + /* Status-Line */ + reply->data = talloc_asprintf(mem_ctx, "HTTP/1.1 %d %s\r\n", + status_code, reason_phrase); + if (!reply->data) return ENOMEM; + + /* Headers */ + for (int i = 0; i < num_headers; i++) { + ret = sec_http_append_header(mem_ctx, &reply->data, + headers[i].name, headers[i].value); + if (ret) return ret; + } + + /* CRLF separator before body */ + reply->data = talloc_strdup_append_buffer(reply->data, "\r\n"); + + reply->length = strlen(reply->data); + + /* Message-Body */ + if (body && body->length) { + reply->data = talloc_realloc(mem_ctx, reply->data, char, + reply->length + body->length); + if (!reply->data) return ENOMEM; + + memcpy(&reply->data[reply->length], body->data, body->length); + reply->length += body->length; + } + + return EOK; +} + enum sec_http_status_codes sec_errno_to_http_status(errno_t err) { switch (err) { @@ -358,3 +428,36 @@ done: free(jsonized); return ret; } + +int sec_get_provider(struct sec_ctx *sctx, const char *name, + struct provider_handle **out_handle) +{ + struct provider_handle *handle; + + for (int i = 0; sctx->providers && sctx->providers[i]; i++) { + handle = sctx->providers[i]; + if (strcasecmp(handle->name, name) != 0) { + continue; + } + *out_handle = handle; + return EOK; + } + return ENOENT; +} + +int sec_add_provider(struct sec_ctx *sctx, struct provider_handle *handle) +{ + int c; + + for (c = 0; sctx->providers && sctx->providers[c]; c++) + continue; + + sctx->providers = talloc_realloc(sctx, sctx->providers, + struct provider_handle *, c + 2); + if (!sctx->providers) return ENOMEM; + + sctx->providers[c] = talloc_steal(sctx, handle); + sctx->providers[c + 1] = NULL; + + return EOK; +} diff --git a/src/responder/secrets/proxy.c b/src/responder/secrets/proxy.c new file mode 100644 index 0000000000000000000000000000000000000000..13321c13e73b7e8ddf6611dc23d2a9a733bd1b6e --- /dev/null +++ b/src/responder/secrets/proxy.c @@ -0,0 +1,977 @@ +/* + 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 "util/crypto/sss_crypto.h" +#include "resolv/async_resolv.h" +#include "util/sss_sockets.h" + +struct proxy_context { + struct resolv_ctx *resctx; + struct confdb_ctx *cdb; +}; + +enum proxy_auth_type { + PAT_NONE = 0, + PAT_BASIC_AUTH = 1, + PAT_HEADER = 2, +}; + +struct pat_basic_auth { + char *username; + char *password; +}; + +struct pat_header { + char *name; + char *value; +}; + +struct proxy_cfg { + char *url; + char **fwd_headers; + int num_headers; + enum proxy_auth_type auth_type; + union { + struct pat_basic_auth basic; + struct pat_header header; + } auth; +}; + +static int proxy_get_config_string(struct proxy_context *pctx, + TALLOC_CTX *ctx, bool not_null, + struct sec_req_ctx *secreq, + const char *name, char **value) +{ + int ret; + + ret = confdb_get_string(pctx->cdb, ctx, + secreq->cfg_section, name, NULL, value); + if (not_null && (ret == 0) && (*value == NULL)) ret = EINVAL; + return ret; +} + +static int proxy_sec_get_cfg(struct proxy_context *pctx, + TALLOC_CTX *mem_ctx, + struct sec_req_ctx *secreq, + struct proxy_cfg **target) +{ + struct proxy_cfg *cfg; + char *auth_type; + int ret; + + /* find matching remote and build the URI */ + cfg = talloc_zero(mem_ctx, struct proxy_cfg); + if (!cfg) return ENOMEM; + + ret = proxy_get_config_string(pctx, cfg, true, secreq, + "proxy_url", &cfg->url); + if (ret) goto done; + + ret = proxy_get_config_string(pctx, cfg, false, secreq, + "auth_type", &auth_type); + if (ret) goto done; + + if (auth_type) { + if (strcmp(auth_type, "basic_auth") == 0) { + cfg->auth_type = PAT_BASIC_AUTH; + ret = proxy_get_config_string(pctx, cfg, true, secreq, "username", + &cfg->auth.basic.username); + if (ret) goto done; + ret = proxy_get_config_string(pctx, cfg, true, secreq, "password", + &cfg->auth.basic.password); + if (ret) goto done; + } else if (strcmp(auth_type, "header") == 0) { + cfg->auth_type = PAT_HEADER; + ret = proxy_get_config_string(pctx, cfg, true, secreq, + "auth_header_name", + &cfg->auth.header.name); + if (ret) goto done; + ret = proxy_get_config_string(pctx, cfg, true, secreq, + "auth_header_value", + &cfg->auth.header.value); + if (ret) goto done; + } else { + ret = EINVAL; + goto done; + } + } + + ret = confdb_get_string_as_list(pctx->cdb, cfg, secreq->cfg_section, + "forward_headers", &cfg->fwd_headers); + if ((ret != 0) && (ret != ENOENT)) goto done; + + while (cfg->fwd_headers && cfg->fwd_headers[cfg->num_headers]) { + cfg->num_headers++; + } + + /* Always whitelist Content-Type and Content-Length */ + cfg->fwd_headers = talloc_realloc(cfg, cfg->fwd_headers, char *, + cfg->num_headers + 3); + if (!cfg->fwd_headers) { + ret = ENOMEM; + goto done; + } + cfg->fwd_headers[cfg->num_headers] = talloc_strdup(cfg, "Content-Type"); + if (!cfg->fwd_headers[cfg->num_headers]) { + ret = ENOMEM; + goto done; + } + cfg->num_headers++; + cfg->fwd_headers[cfg->num_headers] = talloc_strdup(cfg, "Content-Length"); + if (!cfg->fwd_headers[cfg->num_headers]) { + ret = ENOMEM; + goto done; + } + cfg->num_headers++; + cfg->fwd_headers[cfg->num_headers] = NULL; + ret = EOK; + +done: + if (ret) talloc_free(cfg); + else *target = cfg; + return ret; +} + +#define REQ_HAS_SCHEMA(secreq) ((secreq)->parsed_url.schema != NULL) +#define REQ_HAS_HOST(secreq) ((secreq)->parsed_url.host != NULL) +#define REQ_HAS_PORT(secreq) ((secreq)->parsed_url.port != 0) +#define REQ_HAS_PATH(secreq) ((secreq)->parsed_url.path != NULL) +#define REQ_HAS_QUERY(secreq) ((secreq)->parsed_url.query != NULL) +#define REQ_HAS_FRAGMENT(secreq) ((secreq)->parsed_url.fragment != NULL) +#define REQ_HAS_USERINFO(secreq) ((secreq)->parsed_url.userinfo != NULL) + +#define SECREQ_HAS_PORT(secreq) ((secreq)->parsed_url.port != 0) +#define SECREQ_PORT(secreq) ((secreq)->parsed_url.port) + +#define SECREQ_HAS_PART(secreq, part) ((secreq)->parsed_url.part != NULL) +#define SECREQ_PART(secreq, part) \ + ((secreq)->parsed_url.part ? (secreq)->parsed_url.part : "") + +int proxy_sec_map_url(TALLOC_CTX *mem_ctx, struct sec_req_ctx *secreq, + struct proxy_cfg *pcfg, char **req_url) +{ + char port[6] = { 0 }; + char *url; + int blen; + int ret; + + if (SECREQ_HAS_PORT(secreq)) { + ret = snprintf(port, 6, "%d", SECREQ_PORT(secreq)); + if (ret < 1 || ret > 5) return EINVAL; + } + + blen = strlen(secreq->base_path); + + url = talloc_asprintf(mem_ctx, "%s%s%s%s%s%s%s%s/%s%s%s%s%s", + SECREQ_PART(secreq, schema), + SECREQ_HAS_PART(secreq, schema) ? "://" : "", + SECREQ_PART(secreq, userinfo), + SECREQ_HAS_PART(secreq, userinfo) ? "@" : "", + SECREQ_PART(secreq, host), + SECREQ_HAS_PORT(secreq) ? ":" : "", + SECREQ_HAS_PORT(secreq) ? port : "", + pcfg->url, &secreq->mapped_path[blen], + SECREQ_HAS_PART(secreq, query) ? "?" :"", + SECREQ_PART(secreq, query), + SECREQ_HAS_PART(secreq, fragment) ? "?" :"", + SECREQ_PART(secreq, fragment)); + if (!url) return ENOMEM; + + *req_url = url; + return EOK; +} + +int proxy_sec_map_headers(TALLOC_CTX *mem_ctx, struct sec_req_ctx *secreq, + struct proxy_cfg *pcfg, char **req_headers) +{ + int ret; + + for (int i = 0; i < secreq->num_headers; i++) { + bool forward = false; + for (int j = 0; pcfg->fwd_headers[j]; j++) { + if (strcasecmp(secreq->headers[i].name, + pcfg->fwd_headers[j]) == 0) { + forward = true; + break; + } + } + if (forward) { + ret = sec_http_append_header(mem_ctx, req_headers, + secreq->headers[i].name, + secreq->headers[i].value); + if (ret) return ret; + } + } + + if (pcfg->auth_type == PAT_HEADER) { + ret = sec_http_append_header(mem_ctx, req_headers, + pcfg->auth.header.name, + pcfg->auth.header.value); + if (ret) return ret; + } + + return EOK; +} + +static int proxy_http_create_request(TALLOC_CTX *mem_ctx, + struct sec_req_ctx *secreq, + struct proxy_cfg *pcfg, + const char *http_uri, + struct sec_data **http_req) +{ + struct sec_data *req; + int ret; + + req = talloc_zero(mem_ctx, struct sec_data); + if (!req) return ENOMEM; + + /* Request-Line */ + req->data = talloc_asprintf(req, "%s %s HTTP/1.1\r\n", + http_method_str(secreq->method), http_uri); + if (!req->data) { + ret = ENOMEM; + goto done; + } + + /* Headers */ + ret = proxy_sec_map_headers(req, secreq, pcfg, &req->data); + if (ret) goto done; + + /* CRLF separator before body */ + req->data = talloc_strdup_append_buffer(req->data, "\r\n"); + + req->length = strlen(req->data); + + /* Message-Body */ + if (secreq->body.length > 0) { + req->data = talloc_realloc_size(req, req->data, + req->length + secreq->body.length); + if (!req->data) { + ret = ENOMEM; + goto done; + } + + memcpy(&req->data[req->length], + secreq->body.data, secreq->body.length); + req->length += secreq->body.length; + } + + *http_req = req; + ret = EOK; + +done: + if (ret) talloc_free(req); + return ret; +} + +struct proxy_http_request { + struct sec_data *data; + size_t written; +}; + +struct proxy_http_reply { + http_parser parser; + bool complete; + + int status_code; + char *reason_phrase; + struct sec_kvp *headers; + int num_headers; + struct sec_data body; + + size_t received; +}; + +struct proxy_http_req_state { + struct tevent_context *ev; + + char *proxyname; + int port; + + struct resolv_hostent *hostent; + int hostidx; + + int sd; + struct tevent_fd *fde; + + struct proxy_http_request request; + struct proxy_http_reply *reply; +}; + +static int proxy_http_req_state_destroy(void *data); +static void proxy_http_req_gethostname_done(struct tevent_req *subreq); +static void proxy_http_req_connect_step(struct tevent_req *req); +static void proxy_http_req_connect_done(struct tevent_req *subreq); +static void proxy_fd_handler(struct tevent_context *ev, struct tevent_fd *fde, + uint16_t flags, void *ptr); + +struct tevent_req *proxy_http_req_send(struct proxy_context *pctx, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sec_req_ctx *secreq, + const char *http_uri, + struct sec_data *http_req) +{ + struct proxy_http_req_state *state; + struct http_parser_url parsed; + struct tevent_req *req, *subreq; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct proxy_http_req_state); + if (!req) return NULL; + + state->ev = ev; + state->request.data = http_req; + state->sd = -1; + talloc_set_destructor((TALLOC_CTX *)state, + proxy_http_req_state_destroy); + + /* STEP1: reparse URL to get hostname and port */ + ret = http_parser_parse_url(http_uri, strlen(http_uri), 0, &parsed); + if (ret) goto done; + + if (!(parsed.field_set & (1 << UF_HOST))) { + ret = EINVAL; + goto done; + } + state->proxyname = + talloc_strndup(state, + &http_uri[parsed.field_data[UF_HOST].off], + parsed.field_data[UF_HOST].len); + if (!state->proxyname) { + ret = ENOMEM; + goto done; + } + + if (parsed.field_set & (1 << UF_PORT)) { + state->port = parsed.port; + } else if (parsed.field_set & (1 << UF_SCHEMA)) { + uint16_t off = parsed.field_data[UF_SCHEMA].off; + uint16_t len = parsed.field_data[UF_SCHEMA].len; + + if ((len == 5) && + (strncmp("https", &http_uri[off], len) == 0)) { + state->port = 443; + } else if ((len == 4) && + (strncmp("http", &http_uri[off], len) == 0)) { + state->port = 80; + } + } + + /* STEP2: resolve hostname first */ + subreq = resolv_gethostbyname_send(state, ev, pctx->resctx, + state->proxyname, IPV4_FIRST, + default_host_dbs); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, proxy_http_req_gethostname_done, req); + + return req; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void proxy_http_req_gethostname_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct proxy_http_req_state *state; + int resolv_status; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct proxy_http_req_state); + + ret = resolv_gethostbyname_recv(subreq, state, &resolv_status, NULL, + &state->hostent); + talloc_zfree(subreq); + if (ret != EOK) { + if (ret == ENOENT) { + /* Empty result, just quit */ + DEBUG(SSSDBG_TRACE_INTERNAL, "No hostent found\n"); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Could not resolve fqdn for this machine, error [%d]: %s, " + "resolver returned: [%d]: %s\n", ret, strerror(ret), + resolv_status, resolv_strerror(resolv_status)); + } + goto done; + } + + /* EOK */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Found fqdn: %s\n", state->hostent->name); + + /* STEP3: connect to one of the servers */ + proxy_http_req_connect_step(req); + return; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +static void proxy_http_req_connect_step(struct tevent_req *req) +{ + struct proxy_http_req_state *state; + struct sockaddr_storage *sockaddr; + char *ipaddr; + struct tevent_req *subreq; + int ret; + + state = tevent_req_data(req, struct proxy_http_req_state); + + if (!state->hostent->addr_list[state->hostidx]) { + DEBUG(SSSDBG_CRIT_FAILURE, "No more addresses to try.\n"); + ret = ENXIO; + goto done; + } + + sockaddr = resolv_get_sockaddr_address_index(state, state->hostent, + state->port, state->hostidx); + if (sockaddr == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "resolv_get_sockaddr_address() failed\n"); + ret = EIO; + goto done; + } + + if (DEBUG_IS_SET(SSSDBG_TRACE_FUNC)) { + ipaddr = resolv_get_string_address_index(state, state->hostent, + state->hostidx); + if (!ipaddr) { + ret = EFAULT; + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, "Connecting to %s:%d\n", + ipaddr, state->port); + } + + /* increase idx for next attempt */ + state->hostidx++; + + subreq = sssd_async_socket_init_send(state, state->ev, sockaddr, + sizeof(struct sockaddr_storage), + SEC_NET_TIMEOUT); + if (!subreq) { + ret = EIO; + goto done; + } + tevent_req_set_callback(subreq, proxy_http_req_connect_done, req); + return; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +static void proxy_http_req_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct proxy_http_req_state *state; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct proxy_http_req_state); + + ret = sssd_async_socket_init_recv(subreq, &state->sd); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sssd_async_socket_init request failed: [%d]: %s.\n", + ret, sss_strerror(ret)); + + /* try next server if any */ + proxy_http_req_connect_step(req); + return; + } + + /* EOK */ + DEBUG(SSSDBG_TRACE_FUNC, "Connected to %s\n", state->hostent->name); + + state->fde = tevent_add_fd(state->ev, state, state->sd, + TEVENT_FD_WRITE, proxy_fd_handler, + req); + if (!state->fde) { + ret = EIO; + goto done; + } + + return; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + + +int proxy_http_req_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct proxy_http_reply **reply) +{ + struct proxy_http_req_state *state = + tevent_req_data(req, struct proxy_http_req_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *reply = talloc_move(mem_ctx, &state->reply); + + return EOK; +} + +static int proxy_http_req_state_destroy(void *data) +{ + struct proxy_http_req_state *state = + talloc_get_type(data, struct proxy_http_req_state); + + if (!state) return 0; + + if (state->sd != -1) { + DEBUG(SSSDBG_TRACE_FUNC, "closing socket [%d]\n", state->sd); + close(state->sd); + state->sd = -1; + } + + return 0; +} + +static int proxy_wire_send(int fd, struct proxy_http_request *req) +{ + struct sec_data data; + size_t ret; + + data.data = req->data->data + req->written; + data.length = req->data->length - req->written; + + ret = sec_send_data(fd, &data); + if (ret != EOK && ret != EAGAIN) return ret; + + req->written = req->data->length - data.length; + return ret; +} + +static void proxy_fd_send(void *data) +{ + struct proxy_http_req_state *state; + struct tevent_req * req; + int ret; + + req = talloc_get_type(data, struct tevent_req); + state = tevent_req_data(req, struct proxy_http_req_state); + + ret = proxy_wire_send(state->sd, &state->request); + if (ret == EAGAIN) { + /* not all data was sent, loop again */ + return; + } + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to send data, aborting!\n"); + tevent_req_error(req, ret); + return; + } + + /* ok all sent, wait for reply now */ + TEVENT_FD_NOT_WRITEABLE(state->fde); + TEVENT_FD_READABLE(state->fde); + return; +} + +static bool ph_received_data(struct proxy_http_reply *reply, size_t length) +{ + reply->received += length; + if (reply->received > SEC_REQUEST_MAX_SIZE) { + DEBUG(SSSDBG_FATAL_FAILURE, "Request too big, aborting!\n"); + return true; + } + return false; +} + +static void ph_append_string(TALLOC_CTX *memctx, char **dest, + const char *src, size_t len) +{ + if (*dest) { + *dest = talloc_strndup_append_buffer(*dest, src, len); + } else { + *dest = talloc_strndup(memctx, src, len); + } +} + +static int ph_on_message_begin(http_parser *parser) +{ + DEBUG(SSSDBG_TRACE_INTERNAL, "HTTP Message parsing begins\n"); + return 0; +} + +#if ((HTTP_PARSER_VERSION_MAJOR >= 2) && (HTTP_PARSER_VERSION_MINOR >= 2)) +static int ph_on_status(http_parser *parser, const char *at, size_t length) +{ + struct proxy_http_reply *reply = + talloc_get_type(parser->data, struct proxy_http_reply); + + if (ph_received_data(reply, length)) return -1; + + ph_append_string(reply, &reply->reason_phrase, at, length); + if (!reply->reason_phrase) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to store reason phrase, aborting client!\n"); + return -1; + } + + return 0; +} +#endif + +static int ph_on_header_field(http_parser *parser, + const char *at, size_t length) +{ + struct proxy_http_reply *reply = + talloc_get_type(parser->data, struct proxy_http_reply); + int n = reply->num_headers; + + if (ph_received_data(reply, length)) return -1; + + if (!reply->headers) { + reply->headers = talloc_zero_array(reply, struct sec_kvp, 10); + } else if ((n % 10 == 0) && + (reply->headers[n - 1].value)) { + reply->headers = talloc_realloc(reply, reply->headers, + struct sec_kvp, n + 10); + if (reply->headers) { + memset(&reply->headers[n], 0, sizeof(struct sec_kvp) * 10); + } + } + if (!reply->headers) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to store headers, aborting client!\n"); + return -1; + } + + if (!n || reply->headers[n - 1].value) { + /* new field */ + n++; + } + ph_append_string(reply->headers, &reply->headers[n - 1].name, at, length); + if (!reply->headers[n - 1].name) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to store header name, aborting client!\n"); + return -1; + } + + return 0; +} + +static int ph_on_header_value(http_parser *parser, + const char *at, size_t length) +{ + struct proxy_http_reply *reply = + talloc_get_type(parser->data, struct proxy_http_reply); + int n = reply->num_headers; + + if (ph_received_data(reply, length)) return -1; + + if (!reply->headers) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Invalid headers pointer, aborting client!\n"); + return -1; + } + + if (reply->headers[n].name && !reply->headers[n].value) { + /* we increment on new value */ + n = ++reply->num_headers; + } + + ph_append_string(reply->headers, &reply->headers[n - 1].value, at, length); + if (!reply->headers[n - 1].value) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to store header value, aborting client!\n"); + return -1; + } + + return 0; +} + +static int ph_on_headers_complete(http_parser *parser) +{ + /* TODO: if message has no body we should return 1 */ + return 0; +} + +static int ph_on_body(http_parser *parser, const char *at, size_t length) +{ + struct proxy_http_reply *reply = + talloc_get_type(parser->data, struct proxy_http_reply); + + if (ph_received_data(reply, length)) return -1; + + /* FIXME: body may be binary */ + ph_append_string(reply, &reply->body.data, at, length); + if (!reply->body.data) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to store body, aborting!\n"); + return -1; + } + reply->body.length += length; + + return 0; +} + +static int ph_on_message_complete(http_parser *parser) +{ + struct proxy_http_reply *reply = + talloc_get_type(parser->data, struct proxy_http_reply); + + reply->status_code = parser->status_code; + reply->complete = true; + + return 0; +} + +static http_parser_settings ph_callbacks = { + .on_message_begin = ph_on_message_begin, +#if ((HTTP_PARSER_VERSION_MAJOR >= 2) && (HTTP_PARSER_VERSION_MINOR >= 2)) + .on_status = ph_on_status, +#endif + .on_header_field = ph_on_header_field, + .on_header_value = ph_on_header_value, + .on_headers_complete = ph_on_headers_complete, + .on_body = ph_on_body, + .on_message_complete = ph_on_message_complete +}; + +static void proxy_fd_recv(void *data) +{ + char buffer[SEC_PACKET_MAX_RECV_SIZE]; + struct sec_data packet = { buffer, + SEC_PACKET_MAX_RECV_SIZE }; + struct proxy_http_req_state *state; + struct tevent_req *req; + bool must_complete = false; + int ret; + + req = talloc_get_type(data, struct tevent_req); + state = tevent_req_data(req, struct proxy_http_req_state); + + if (!state->reply) { + /* A new reply */ + state->reply = talloc_zero(state, struct proxy_http_reply); + if (!state->reply) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to allocate reply, aborting!\n"); + tevent_req_error(req, ENOMEM); + return; + } + http_parser_init(&state->reply->parser, HTTP_RESPONSE); + state->reply->parser.data = state->reply; + } + + ret = sec_recv_data(state->sd, &packet); + switch (ret) { + case ENODATA: + DEBUG(SSSDBG_TRACE_ALL, "Server closed connection.\n"); + /* if we got no content length and the request is not complete, + * then 0 length will indicate EOF to the parser, otherwise we + * have an error */ + must_complete = true; + break; + case EAGAIN: + DEBUG(SSSDBG_TRACE_ALL, + "Interrupted before any data could be read, retry later\n"); + return; + case EOK: + /* all fine */ + break; + default: + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to receive data (%d, %s), aborting\n", + ret, sss_strerror(ret)); + tevent_req_error(req, EIO); + return; + } + + ret = http_parser_execute(&state->reply->parser, &ph_callbacks, + packet.data, packet.length); + if (ret != packet.length) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to parse request, aborting!\n"); + tevent_req_error(req, EIO); + return; + } + + if (!state->reply->complete) { + if (must_complete) { + tevent_req_error(req, EIO); + } + return; + } + + /* do not read anymore, server is done sending */ + TEVENT_FD_NOT_READABLE(state->fde); + tevent_req_done(req); +} + +static void proxy_fd_handler(struct tevent_context *ev, struct tevent_fd *fde, + uint16_t flags, void *data) +{ + if (flags & TEVENT_FD_READ) { + proxy_fd_recv(data); + } else if (flags & TEVENT_FD_WRITE) { + proxy_fd_send(data); + } +} + +struct proxy_secret_state { + struct tevent_context *ev; + struct sec_req_ctx *secreq; + struct proxy_cfg *pcfg; +}; +static void proxy_secret_req_done(struct tevent_req *subreq); + +struct tevent_req *proxy_secret_req(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *provider_ctx, + struct sec_req_ctx *secreq) +{ + struct tevent_req *req, *subreq; + struct proxy_secret_state *state; + struct proxy_context *pctx; + struct sec_data *http_req; + char *http_uri; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct proxy_secret_state); + if (!req) return NULL; + + state->ev = ev; + state->secreq = secreq; + + pctx = talloc_get_type(provider_ctx, struct proxy_context); + if (!pctx) { + ret = EIO; + goto done; + } + + ret = proxy_sec_get_cfg(pctx, state, state->secreq, &state->pcfg); + if (ret) goto done; + + ret = proxy_sec_map_url(state, secreq, state->pcfg, &http_uri); + if (ret) goto done; + + ret = proxy_http_create_request(state, state->secreq, state->pcfg, + http_uri, &http_req); + if (ret) goto done; + + subreq = proxy_http_req_send(pctx, state, ev, state->secreq, + http_uri, http_req); + if (!subreq) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, proxy_secret_req_done, req); + + return req; + +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 have been + * made if we get here */ + tevent_req_done(req); + } + + return tevent_req_post(req, ev); +} + +static void proxy_secret_req_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct proxy_secret_state *state; + struct proxy_http_reply *reply = NULL; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct proxy_secret_state); + + ret = proxy_http_req_recv(subreq, state, &reply); + talloc_zfree(subreq); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = sec_http_reply_with_headers(state->secreq, &state->secreq->reply, + reply->status_code, reply->reason_phrase, + reply->headers, reply->num_headers, + &reply->body); + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +struct provider_handle proxy_secrets_handle = { + .fn = proxy_secret_req, + .context = NULL, +}; + +int proxy_secrets_provider_handle(struct sec_ctx *sctx, + struct provider_handle **out_handle) +{ + struct provider_handle *handle; + struct proxy_context *pctx; + + handle = talloc_zero(sctx, struct provider_handle); + if (!handle) return ENOMEM; + + handle->name = "PROXY"; + handle->fn = proxy_secret_req; + + pctx = talloc(handle, struct proxy_context); + if (!pctx) return ENOMEM; + + pctx->resctx = sctx->resctx; + pctx->cdb = sctx->rctx->cdb; + + handle->context = pctx; + + *out_handle = handle; + return EOK; +} diff --git a/src/responder/secrets/secsrv.c b/src/responder/secrets/secsrv.c index 523aa6469da9939abf9a7fbbd159234e7ed0982b..6f8052bdf25fe903683d6b26d92b2a4c31743470 100644 --- a/src/responder/secrets/secsrv.c +++ b/src/responder/secrets/secsrv.c @@ -26,6 +26,7 @@ #include "responder/common/responder.h" #include "responder/secrets/secsrv.h" +#include "resolv/async_resolv.h" #define DEFAULT_SEC_FD_LIMIT 2048 @@ -115,6 +116,12 @@ static int sec_process_init(TALLOC_CTX *mem_ctx, goto fail; } + ret = resolv_init(sctx, ev, SEC_NET_TIMEOUT, &sctx->resctx); + if (ret != EOK) { + /* not fatal for now */ + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to initialize resolver library\n"); + } + /* Set up file descriptor limits */ responder_set_fd_limit(sctx->fd_limit); diff --git a/src/responder/secrets/secsrv.h b/src/responder/secrets/secsrv.h index 91535ae2bf6155483b3b0dc789b704291009e6cb..ace30f86adb975d3c212685ebe70b4600c6f2776 100644 --- a/src/responder/secrets/secsrv.h +++ b/src/responder/secrets/secsrv.h @@ -30,9 +30,16 @@ #include #include +#define SEC_NET_TIMEOUT 5 + +struct resctx; + struct sec_ctx { + struct resolv_ctx *resctx; struct resp_ctx *rctx; int fd_limit; + + struct provider_handle **providers; }; int sec_connection_setup(struct cli_ctx *cctx); diff --git a/src/responder/secrets/secsrv_cmd.c b/src/responder/secrets/secsrv_cmd.c index 00f0600d0b110a62821563e7aa34c985babbe886..58340bae4f4a2de67f7e187682f9b8e4def12877 100644 --- a/src/responder/secrets/secsrv_cmd.c +++ b/src/responder/secrets/secsrv_cmd.c @@ -25,9 +25,6 @@ #include "responder/secrets/secsrv.h" #include "responder/secrets/secsrv_private.h" -#define SEC_REQUEST_MAX_SIZE 65536 -#define SEC_PACKET_MAX_RECV_SIZE 8192 - /* ##### Request Handling ##### */ @@ -376,7 +373,7 @@ static int sec_on_message_complete(http_parser *parser) /* ##### Communications ##### */ -static int sec_send_data(int fd, struct sec_data *data) +int sec_send_data(int fd, struct sec_data *data) { ssize_t len; @@ -424,7 +421,7 @@ static void sec_send(struct cli_ctx *cctx) return; } -static int sec_recv_data(int fd, struct sec_data *data) +int sec_recv_data(int fd, struct sec_data *data) { ssize_t len; @@ -439,6 +436,7 @@ static int sec_recv_data(int fd, struct sec_data *data) } if (len == 0) { + data->length = 0; return ENODATA; } diff --git a/src/responder/secrets/secsrv_local.h b/src/responder/secrets/secsrv_local.h index d21b14953b0497a17f7b0b7730a3e1529f928aea..1ec1a343eff08b9377d1106035a99f8169e5d75e 100644 --- a/src/responder/secrets/secsrv_local.h +++ b/src/responder/secrets/secsrv_local.h @@ -22,7 +22,7 @@ #ifndef __SECSRV_LOCAL_H__ #define __SECSRV_LOCAL_H__ -int local_secrets_provider_handle(TALLOC_CTX *mem_ctx, - struct provider_handle **handle); +int local_secrets_provider_handle(struct sec_ctx *sctx, + struct provider_handle **out_handle); #endif /* __SECSRV_LOCAL_H__ */ diff --git a/src/responder/secrets/secsrv_private.h b/src/responder/secrets/secsrv_private.h index 4b55f645a140e7650750196f7706ba183765efb3..ce8cf821620ff25039d38e00eb91a6e4d24dd093 100644 --- a/src/responder/secrets/secsrv_private.h +++ b/src/responder/secrets/secsrv_private.h @@ -24,6 +24,7 @@ #include "config.h" #include "responder/common/responder.h" +#include "responder/secrets/secsrv.h" #include struct sec_kvp { @@ -58,13 +59,14 @@ struct sec_url { char *host; int port; char *path; + char *query; char *fragment; char *userinfo; - char *query; }; struct sec_req_ctx { struct cli_ctx *cctx; + const char *base_path; const char *cfg_section; bool complete; @@ -73,7 +75,7 @@ struct sec_req_ctx { char *request_url; char *mapped_path; - uint8_t method; + enum http_method method; struct sec_url parsed_url; struct sec_kvp *headers; int num_headers; @@ -88,9 +90,13 @@ typedef struct tevent_req *(*sec_provider_req_t)(TALLOC_CTX *mem_ctx, struct sec_req_ctx *secreq); struct provider_handle { + const char *name; sec_provider_req_t fn; void *context; }; +int sec_get_provider(struct sec_ctx *sctx, const char *name, + struct provider_handle **out_handle); +int sec_add_provider(struct sec_ctx *sctx, struct provider_handle *handle); #define SEC_BASEPATH "/secrets/" @@ -99,12 +105,19 @@ 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_append_header(TALLOC_CTX *mem_ctx, char **dest, + char *field, char *value); + 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); +int sec_http_reply_with_headers(TALLOC_CTX *mem_ctx, struct sec_data *reply, + int status_code, const char *reason, + struct sec_kvp *headers, int num_headers, + 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, @@ -118,4 +131,11 @@ int sec_array_to_json(TALLOC_CTX *mem_ctx, char **array, int count, char **output); +/* secsrv_cmd.c */ +#define SEC_REQUEST_MAX_SIZE 65536 +#define SEC_PACKET_MAX_RECV_SIZE 8192 + +int sec_send_data(int fd, struct sec_data *data); +int sec_recv_data(int fd, struct sec_data *data); + #endif /* __SECSRV_PRIVATE_H__ */ diff --git a/src/responder/secrets/secsrv_local.h b/src/responder/secrets/secsrv_proxy.h similarity index 77% copy from src/responder/secrets/secsrv_local.h copy to src/responder/secrets/secsrv_proxy.h index d21b14953b0497a17f7b0b7730a3e1529f928aea..cfce721b9071c61ef143e4b9c3cd16046dec19d9 100644 --- a/src/responder/secrets/secsrv_local.h +++ b/src/responder/secrets/secsrv_proxy.h @@ -19,10 +19,10 @@ along with this program. If not, see . */ -#ifndef __SECSRV_LOCAL_H__ -#define __SECSRV_LOCAL_H__ +#ifndef __SECSRV_PROXY_H__ +#define __SECSRV_PROXY_H__ -int local_secrets_provider_handle(TALLOC_CTX *mem_ctx, - struct provider_handle **handle); +int proxy_secrets_provider_handle(struct sec_ctx *sctx, + struct provider_handle **out_handle); -#endif /* __SECSRV_LOCAL_H__ */ +#endif /* __SECSRV_PROXY_H__ */ -- 2.4.11