/*
   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 "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;
}
