From c95e3fab9ce6fef97e4318fe48cef80574386138 Mon Sep 17 00:00:00 2001
From: Simo Sorce <simo@redhat.com>
Date: Tue, 12 Jan 2016 14:34:11 -0500
Subject: [PATCH 10/15] Add initial providers infrastructure.

Also adds support for the basic LOCAL provider that stores data
on the local machine.

Signed-off-by: Simo Sorce <simo@redhat.com>
---
 Makefile.am                                        |  11 +
 src/responder/secrets/local.c                      | 427 +++++++++++++++++++++
 src/responder/secrets/providers.c                  | 354 +++++++++++++++++
 src/responder/secrets/secsrv.h                     |   2 -
 src/responder/secrets/secsrv_cmd.c                 | 183 ++++++---
 src/responder/secrets/{secsrv.h => secsrv_local.h} |  26 +-
 src/responder/secrets/secsrv_private.h             | 121 ++++++
 7 files changed, 1042 insertions(+), 82 deletions(-)
 create mode 100644 src/responder/secrets/local.c
 create mode 100644 src/responder/secrets/providers.c
 copy src/responder/secrets/{secsrv.h => secsrv_local.h} (67%)
 create mode 100644 src/responder/secrets/secsrv_private.h

diff --git a/Makefile.am b/Makefile.am
index 504cca61d5c5dd8c875a64b4704975d065ccb6bb..fd9f493bedf319a0c38d224d8d7e1fc981f1a06d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -79,6 +79,8 @@ krb5rcachedir = @krb5rcachedir@
 sudolibdir = @sudolibpath@
 polkitdir = @polkitdir@
 
+secdbpath = @secdbpath@
+
 UNICODE_LIBS=@UNICODE_LIBS@
 
 MKDIR_P = @MKDIR_P@
@@ -86,6 +88,7 @@ INSTALL = @INSTALL@
 
 SSSD_USER = @SSSD_USER@
 
+
 AM_CFLAGS =
 if WANT_AUX_INFO
     AM_CFLAGS += -aux-info $@.X
@@ -590,6 +593,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 \
@@ -1237,9 +1242,12 @@ 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)
 sssd_secrets_LDADD = \
     $(HTTP_PARSER_LIBS) \
+    $(JANSSON_LIBS) \
     $(TDB_LIBS) \
     $(SSSD_LIBS) \
     $(SSSD_INTERNAL_LTLIBS)
@@ -3582,6 +3590,9 @@ endif
             $(DESTDIR)$(pubconfpath) \
             $(DESTDIR)$(pubconfpath)/krb5.include.d $(DESTDIR)$(gpocachepath)
 	$(INSTALL) -d -m 0711 $(DESTDIR)$(sssdconfdir)
+if BUILD_SECRETS
+	$(MKDIR_P) $(DESTDIR)$(secdbpath)
+endif
 
 if HAVE_DOXYGEN
 docs:
diff --git a/src/responder/secrets/local.c b/src/responder/secrets/local.c
new file mode 100644
index 0000000000000000000000000000000000000000..f8e3360b9dc8336b93e512514790c474de6c0b55
--- /dev/null
+++ b/src/responder/secrets/local.c
@@ -0,0 +1,427 @@
+/*
+   SSSD
+
+   Secrets Responder
+
+   Copyright (C) Simo Sorce <ssorce@redhat.com>	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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "responder/secrets/secsrv_private.h"
+#include <ldb.h>
+
+
+
+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, 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, 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..7b5245835fc53b2f876c4f5c62440b93c6f0a114
--- /dev/null
+++ b/src/responder/secrets/providers.c
@@ -0,0 +1,354 @@
+/*
+   SSSD
+
+   Secrets Responder
+
+   Copyright (C) Simo Sorce <ssorce@redhat.com>	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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "responder/secrets/secsrv_private.h"
+#include "responder/secrets/secsrv_local.h"
+#include <jansson.h>
+
+int sec_map_url_to_user_path(struct sec_req_ctx *secreq, char **mapped_path)
+{
+    /* change path to be user specific */
+    *mapped_path =
+        talloc_asprintf(secreq, SEC_BASEPATH"users/%lu/%s",
+                        (long unsigned)client_euid(secreq->cctx->creds),
+                        &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, &sections,
+                                  &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", NULL, &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,
+                        "<html>\r\n"
+                            "<head>\r\n<title>%d %s</title></head>\r\n"
+                        "<body>\r\n"
+                            "<h1>%s</h1>\r\n"
+                            "<p>%s</p>\r\n"
+                        "</body>",
+                        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);
+    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.h b/src/responder/secrets/secsrv.h
index f1bd8660f85cf21a6eaa38161c0aea5ccb9bc816..91535ae2bf6155483b3b0dc789b704291009e6cb 100644
--- a/src/responder/secrets/secsrv.h
+++ b/src/responder/secrets/secsrv.h
@@ -38,5 +38,3 @@ struct sec_ctx {
 int sec_connection_setup(struct cli_ctx *cctx);
 
 #endif /* __SECSRV_H__ */
-
-
diff --git a/src/responder/secrets/secsrv_cmd.c b/src/responder/secrets/secsrv_cmd.c
index dae3b910e0144e2ee7112c4f30114bae781ecc26..bcbbb3b966471d66919c5b09d3f4ebdba8540834 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 <http://www.gnu.org/licenses/>.
 */
 
+#include "config.h"
 #include "util/util.h"
 #include "responder/common/responder.h"
 #include "responder/secrets/secsrv.h"
-#include "confdb/confdb.h"
-#include <http_parser.h>
+#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"
-        "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
-        "<html>\r\n<head>\r\n<title>403 Forbidden</title></head>\r\n"
-        "<body>\r\n<h1>Forbidden</h1>\r\n"
-        "<p>You don't have permission to access the requested "
-        "resource.</p>\r\n<hr>\r\n</body>");
-    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(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(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.h b/src/responder/secrets/secsrv_local.h
similarity index 67%
copy from src/responder/secrets/secsrv.h
copy to src/responder/secrets/secsrv_local.h
index f1bd8660f85cf21a6eaa38161c0aea5ccb9bc816..537f61e53d098deecbc2530e9e3c21a07eaecada 100644
--- a/src/responder/secrets/secsrv.h
+++ b/src/responder/secrets/secsrv_local.h
@@ -1,7 +1,7 @@
 /*
    SSSD
 
-   Secrets Responder, header file
+   Secrets Local Provider
 
    Copyright (C) Simo Sorce <ssorce@redhat.com>	2016
 
@@ -19,24 +19,10 @@
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-#ifndef __SECSRV_H__
-#define __SECSRV_H__
-
-#include "config.h"
-
-#include <stdint.h>
-#include <sys/un.h>
-#include <talloc.h>
-#include <tevent.h>
-#include <ldb.h>
-
-struct sec_ctx {
-    struct resp_ctx *rctx;
-    int fd_limit;
-};
-
-int sec_connection_setup(struct cli_ctx *cctx);
-
-#endif /* __SECSRV_H__ */
+#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 <ssorce@redhat.com>	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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __SECSRV_PRIVATE_H__
+#define __SECSRV_PRIVATE_H__
+
+#include "config.h"
+#include "responder/common/responder.h"
+#include <http_parser.h>
+
+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.5.5

