dpward / rpms / sssd

Forked from rpms/sssd 3 years ago
Clone
Blob Blame History Raw
From 2ca9a394063baac075e05b14fcc6c027027ab8f9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pavel=20B=C5=99ezina?= <pbrezina@redhat.com>
Date: Fri, 24 Feb 2017 10:40:43 +0100
Subject: [PATCH 74/90] tcurl: add support for ssl and raw output

At first, this patch separates curl_easy handle from the multi-handle
processing and makes it encapsulated in custom tcurl_request structure.
This allows us to separate protocol initialization from its asynchonous
logic which gives us the ability to set different options for each
request without over-extending the parameter list.

In this patch we implement options for peer verification for TLS-enabled
protocols and to return response with body and headers together.

Reviewed-by: Simo Sorce <simo@redhat.com>
Reviewed-by: Jakub Hrozek <jhrozek@redhat.com>
(cherry picked from commit 300b9e9217ee1ed8d845ed2370c5ccf5c87afb36)
---
 src/tests/tcurl_test_tool.c |  41 +-
 src/util/tev_curl.c         | 992 +++++++++++++++++++++++++-------------------
 src/util/tev_curl.h         | 172 +++++++-
 src/util/util_errors.c      |   4 +
 src/util/util_errors.h      |   4 +
 5 files changed, 755 insertions(+), 458 deletions(-)

diff --git a/src/tests/tcurl_test_tool.c b/src/tests/tcurl_test_tool.c
index 2af950ebb76a22bdf4a6dfd58442b10486e64293..9a6266f89131ffd3a561e857af85df9854c44949 100644
--- a/src/tests/tcurl_test_tool.c
+++ b/src/tests/tcurl_test_tool.c
@@ -42,9 +42,7 @@ static void request_done(struct tevent_req *req)
     struct tool_ctx *tool_ctx = tevent_req_callback_data(req,
                                                          struct tool_ctx);
 
-    tool_ctx->error = tcurl_http_recv(tool_ctx, req,
-                                      &http_code,
-                                      &outbuf);
+    tool_ctx->error = tcurl_request_recv(tool_ctx, req, &outbuf, &http_code);
     talloc_zfree(req);
 
     if (tool_ctx->error != EOK) {
@@ -87,16 +85,17 @@ int main(int argc, const char *argv[])
           "The path to the HTTP server socket", NULL },
         { "get", 'g', POPT_ARG_NONE, NULL, 'g', "Perform a HTTP GET (default)", NULL },
         { "put", 'p', POPT_ARG_NONE, NULL, 'p', "Perform a HTTP PUT", NULL },
-        { "del", 'd', POPT_ARG_NONE, NULL, 'd', "Perform a HTTP DELETE", NULL },
         { "post", 'o', POPT_ARG_NONE, NULL, 'o', "Perform a HTTP POST", NULL },
+        { "del", 'd', POPT_ARG_NONE, NULL, 'd', "Perform a HTTP DELETE", NULL },
         { "verbose", 'v', POPT_ARG_NONE, NULL, 'v', "Print response code and body", NULL },
         POPT_TABLEEND
     };
 
     struct tevent_req *req;
     struct tevent_context *ev;
-    enum tcurl_http_request req_type = TCURL_HTTP_GET;
+    enum tcurl_http_method method = TCURL_HTTP_GET;
     struct tcurl_ctx *ctx;
+    struct tcurl_request *tcurl_req;
     struct tool_ctx *tool_ctx;
 
     const char *urls[MAXREQ] = { 0 };
@@ -111,16 +110,16 @@ int main(int argc, const char *argv[])
     while ((opt = poptGetNextOpt(pc)) > 0) {
         switch (opt) {
         case 'g':
-            req_type = TCURL_HTTP_GET;
+            method = TCURL_HTTP_GET;
             break;
         case 'p':
-            req_type = TCURL_HTTP_PUT;
-            break;
-        case 'd':
-            req_type = TCURL_HTTP_DELETE;
+            method = TCURL_HTTP_PUT;
             break;
         case 'o':
-            req_type = TCURL_HTTP_POST;
+            method = TCURL_HTTP_POST;
+            break;
+        case 'd':
+            method = TCURL_HTTP_DELETE;
             break;
         case 'v':
             pc_verbose = 1;
@@ -146,7 +145,7 @@ int main(int argc, const char *argv[])
     }
 
     while ((extra_arg_ptr = poptGetArg(pc)) != NULL) {
-        switch (req_type) {
+        switch(method) {
         case TCURL_HTTP_GET:
         case TCURL_HTTP_DELETE:
         case TCURL_HTTP_POST:
@@ -203,14 +202,16 @@ int main(int argc, const char *argv[])
     }
 
     for (size_t i = 0; i < n_reqs; i++) {
-        req = tcurl_http_send(tool_ctx, ev, ctx,
-                              req_type,
-                              socket_path,
-                              urls[i],
-                              headers,
-                              inbufs[i],
-                              5);
-        if (req == NULL) {
+        tcurl_req = tcurl_http(tool_ctx, method, socket_path,
+                               urls[i], headers, inbufs[i]);
+        if (tcurl_req == NULL) {
+            DEBUG(SSSDBG_FATAL_FAILURE, "Unable to create TCURL request\n");
+            talloc_zfree(tool_ctx);
+            return 1;
+        }
+
+        req = tcurl_request_send(tool_ctx, ev, ctx, tcurl_req, 10);
+        if (ctx == NULL) {
             DEBUG(SSSDBG_FATAL_FAILURE, "Could not create request\n");
             talloc_zfree(tool_ctx);
             return 1;
diff --git a/src/util/tev_curl.c b/src/util/tev_curl.c
index 645d1182d10f825f209f48e0ba7e6804dde1971c..c155f4c038d4215933ee30d41c694ad4a14ae132 100644
--- a/src/util/tev_curl.c
+++ b/src/util/tev_curl.c
@@ -34,8 +34,8 @@
 #include "util/util.h"
 #include "util/tev_curl.h"
 
-#define IOBUF_CHUNK   1024
-#define IOBUF_MAX     4096
+#define TCURL_IOBUF_CHUNK   1024
+#define TCURL_IOBUF_MAX     4096
 
 static bool global_is_curl_initialized;
 
@@ -71,39 +71,12 @@ struct tcurl_sock {
     struct tevent_fd *fde;      /* tevent tracker of the fd events */
 };
 
-/**
- * @brief A state of one curl transfer
- *
- * Intentionally breaking the tevent coding style here and making the struct available
- * in the whole module so that the structure is available to curl callbacks that
- * need to access the state of the transfer.
- *
- * @see handle_curlmsg_done()
- */
-struct tcurl_http_state {
-    /* Input parameters */
-    struct tcurl_ctx *tctx;
-    const char *socket_path;
-    const char *url;
-    int timeout;
-    struct sss_iobuf *inbuf;
-
-    /* Internal state */
-    CURL *http_handle;
-    struct curl_slist *curl_headers;
-
-    /* Output data */
-    struct sss_iobuf *outbuf;
-    long http_code;
-};
+static void tcurl_request_done(struct tevent_req *req,
+                               errno_t process_error,
+                               int response_code);
 
 static errno_t curl_code2errno(CURLcode crv)
 {
-    if (crv != CURLE_OK) {
-        DEBUG(SSSDBG_OP_FAILURE,
-              "curl error %d: %s\n", crv, curl_easy_strerror(crv));
-    }
-
     switch (crv) {
     /* HTTP error does not fail the whole request, just returns the error
      * separately
@@ -121,6 +94,47 @@ static errno_t curl_code2errno(CURLcode crv)
         return ENOMEM;
     case CURLE_OPERATION_TIMEDOUT:
         return ETIMEDOUT;
+    case CURLE_SSL_ISSUER_ERROR:
+    case CURLE_SSL_CACERT_BADFILE:
+    case CURLE_SSL_CACERT:
+    case CURLE_SSL_CERTPROBLEM:
+        return ERR_INVALID_CERT;
+
+    case CURLE_SSL_CRL_BADFILE:
+    case CURLE_SSL_SHUTDOWN_FAILED:
+    case CURLE_SSL_ENGINE_INITFAILED:
+    case CURLE_USE_SSL_FAILED:
+    case CURLE_SSL_CIPHER:
+    case CURLE_SSL_ENGINE_SETFAILED:
+    case CURLE_SSL_ENGINE_NOTFOUND:
+    case CURLE_SSL_CONNECT_ERROR:
+        return ERR_SSL_FAILURE;
+    case CURLE_PEER_FAILED_VERIFICATION:
+        return ERR_UNABLE_TO_VERIFY_PEER;
+    case CURLE_COULDNT_RESOLVE_HOST:
+        return ERR_UNABLE_TO_RESOLVE_HOST;
+    default:
+        break;
+    }
+
+    return EIO;
+}
+
+static errno_t curlm_code2errno(CURLcode crv)
+{
+    switch (crv) {
+    case CURLM_OK:
+        return EOK;
+    case CURLM_BAD_SOCKET:
+        return EPIPE;
+    case CURLM_OUT_OF_MEMORY:
+        return ENOMEM;
+    case CURLM_BAD_HANDLE:
+    case CURLM_BAD_EASY_HANDLE:
+    case CURLM_UNKNOWN_OPTION:
+        return EINVAL;
+    case CURLM_INTERNAL_ERROR:
+        return ERR_INTERNAL;
     default:
         break;
     }
@@ -145,22 +159,6 @@ static errno_t tcurl_global_init(void)
     return EOK;
 }
 
-static const char *http_req2str(enum tcurl_http_request req)
-{
-    switch (req) {
-    case TCURL_HTTP_GET:
-        return "GET";
-    case TCURL_HTTP_PUT:
-        return "PUT";
-    case TCURL_HTTP_DELETE:
-        return "DELETE";
-    case TCURL_HTTP_POST:
-        return "POST";
-    }
-
-    return "Uknown request type";
-}
-
 static int curl2tev_flags(int curlflags)
 {
     int flags = 0;
@@ -185,9 +183,9 @@ static void handle_curlmsg_done(CURLMsg *message)
     CURL *easy_handle;
     CURLcode crv;
     struct tevent_req *req;
+    long response_code = 0;
     char *done_url;
     errno_t ret;
-    struct tcurl_http_state *state;
 
     easy_handle = message->easy_handle;
     if (easy_handle == NULL) {
@@ -198,9 +196,8 @@ static void handle_curlmsg_done(CURLMsg *message)
     if (DEBUG_IS_SET(SSSDBG_TRACE_FUNC)) {
         crv = curl_easy_getinfo(easy_handle, CURLINFO_EFFECTIVE_URL, &done_url);
         if (crv != CURLE_OK) {
-            DEBUG(SSSDBG_MINOR_FAILURE,
-                  "Cannot get CURLINFO_EFFECTIVE_URL [%d]: %s\n",
-                  crv, curl_easy_strerror(crv));
+            DEBUG(SSSDBG_MINOR_FAILURE, "Cannot get CURLINFO_EFFECTIVE_URL "
+                  "[%d]: %s\n", crv, curl_easy_strerror(crv));
             /* not fatal since we need this only for debugging */
         } else {
             DEBUG(SSSDBG_TRACE_FUNC, "Handled %s\n", done_url);
@@ -209,38 +206,32 @@ static void handle_curlmsg_done(CURLMsg *message)
 
     crv = curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, (void *) &req);
     if (crv != CURLE_OK) {
-        DEBUG(SSSDBG_CRIT_FAILURE,
-              "Cannot get CURLINFO_PRIVATE [%d]: %s\n",
+        DEBUG(SSSDBG_CRIT_FAILURE, "Cannot get CURLINFO_PRIVATE [%d]: %s\n",
               crv, curl_easy_strerror(crv));
-        return;
-    }
-
-    state = tevent_req_data(req, struct tcurl_http_state);
-    if (state == NULL) {
-        DEBUG(SSSDBG_CRIT_FAILURE, "BUG: request has no state\n");
-        tevent_req_error(req, EFAULT);
-        return;
+        ret = curl_code2errno(crv);
+        goto done;
     }
 
     ret = curl_code2errno(message->data.result);
     if (ret != EOK) {
-        DEBUG(SSSDBG_OP_FAILURE,
-              "curl operation failed [%d]: %s\n", ret, sss_strerror(ret));
-        tevent_req_error(req, ret);
-        return;
+        DEBUG(SSSDBG_OP_FAILURE, "CURL operation failed [%d]: %s\n",
+              ret, sss_strerror(ret));
+        goto done;
     }
 
-    /* If there was no fatal error, let's read the HTTP error code and mark
-     * the request as done
-     */
-    crv = curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &state->http_code);
+    /* If there was no fatal error, let's read the response code
+     * and mark the request as done */
+    crv = curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &response_code);
     if (crv != CURLE_OK) {
-        DEBUG(SSSDBG_OP_FAILURE, "Cannot get HTTP status code\n");
-        tevent_req_error(req, EFAULT);
-        return;
+        DEBUG(SSSDBG_OP_FAILURE, "Cannot get response code\n");
+        ret = curl_code2errno(crv);
+        goto done;
     }
 
-    tevent_req_done(req);
+    ret = EOK;
+
+done:
+    tcurl_request_done(req, ret, response_code);
 }
 
 static void process_curl_activity(struct tcurl_ctx *tctx)
@@ -551,346 +542,42 @@ fail:
     return NULL;
 }
 
-static errno_t tcurl_add_headers(struct tcurl_http_state *state,
-                                 const char *headers[]);
-
-static errno_t tcurl_set_options(struct tcurl_http_state *state,
-                                 struct tevent_req *req,
-                                 enum tcurl_http_request req_type);
-
-static int tcurl_http_cleanup_handle(TALLOC_CTX *ptr);
-
-static size_t tcurl_http_write_data(char *ptr,
-                                    size_t size,
-                                    size_t nmemb,
-                                    void *userdata);
-
-static size_t tcurl_http_read_data(void *ptr,
-                                   size_t size,
-                                   size_t nmemb,
-                                   void *userdata);
-
-struct tevent_req *tcurl_http_send(TALLOC_CTX *mem_ctx,
-                                   struct tevent_context *ev,
-                                   struct tcurl_ctx *tctx,
-                                   enum tcurl_http_request req_type,
-                                   const char *socket_path,
-                                   const char *url,
-                                   const char *headers[],
-                                   struct sss_iobuf *req_data,
-                                   int timeout)
-{
-    errno_t ret;
-    struct tevent_req *req;
-    struct tcurl_http_state *state;
-
-    req = tevent_req_create(mem_ctx, &state, struct tcurl_http_state);
-    if (req == NULL) {
-        return NULL;
-    }
-
-    state->tctx = tctx;
-    state->socket_path = socket_path;
-    state->url = url;
-    state->inbuf = req_data;
-    state->timeout = timeout;
-
-    state->outbuf = sss_iobuf_init_empty(state, IOBUF_CHUNK, IOBUF_MAX);
-    if (state->outbuf == NULL) {
-        ret = ENOMEM;
-        goto fail;
-    }
-
-    DEBUG(SSSDBG_TRACE_FUNC,
-          "HTTP request %s for URL %s\n", http_req2str(req_type), url);
-    talloc_set_destructor((TALLOC_CTX *) state, tcurl_http_cleanup_handle);
-
-    /* All transfer share the same multi handle, but each trasfer has its own
-     * easy handle we can use to set per-transfer options
-     */
-    state->http_handle = curl_easy_init();
-    if (state->http_handle == NULL) {
-        DEBUG(SSSDBG_CRIT_FAILURE, "curl_easy_init failed\n");
-        ret = EIO;
-        goto fail;
-    }
-
-    ret = tcurl_add_headers(state, headers);
-    if (ret != EOK) {
-        DEBUG(SSSDBG_CRIT_FAILURE,
-              "Failed to set CURL headers [%d]: %s\n", ret, sss_strerror(ret));
-        goto fail;
-    }
-
-    ret = tcurl_set_options(state, req, req_type);
-    if (ret != EOK) {
-        DEBUG(SSSDBG_CRIT_FAILURE,
-              "Failed to set CURL options [%d]: %s\n", ret, sss_strerror(ret));
-        goto fail;
-    }
-
-    /* Pass control to the curl handling which will mark the request as
-     * done
-     */
-    curl_multi_add_handle(tctx->multi_handle, state->http_handle);
-
-    return req;
-
-fail:
-    tevent_req_error(req, ret);
-    tevent_req_post(req, ev);
-    return req;
-}
-
-static int tcurl_http_cleanup_handle(TALLOC_CTX *ptr)
-{
-    struct tcurl_http_state *state = talloc_get_type(ptr, struct tcurl_http_state);
-
-    if (state == NULL) {
-        return 0;
-    }
-
-    /* it is safe to pass NULL here */
-    curl_multi_remove_handle(state->tctx->multi_handle, state->http_handle);
-    curl_slist_free_all(state->curl_headers);
-    curl_easy_cleanup(state->http_handle);
-    return 0;
-}
-
-static errno_t tcurl_add_headers(struct tcurl_http_state *state,
-                                 const char *headers[])
-{
-    if (headers == NULL) {
-        return EOK;
-    }
-
-    /* The headers will be freed later in tcurl_http_cleanup_handle */
-    for (int i = 0; headers[i] != NULL; i++) {
-        state->curl_headers = curl_slist_append(state->curl_headers, headers[i]);
-        if (state->curl_headers == NULL) {
-            DEBUG(SSSDBG_CRIT_FAILURE, "Cannot add header %s\n", headers[i]);
-            return ENOMEM;
-        }
-    }
-
-    /* Add a dummy header to suppress libcurl adding Expect 100-continue which
-     * was causing libcurl to always wait for the internal timeout when sending
-     * a PUT/PATCH request
-     */
-    state->curl_headers = curl_slist_append(state->curl_headers, "Expect:");
-    if (state->curl_headers == NULL) {
-        DEBUG(SSSDBG_CRIT_FAILURE, "Cannot add the dummy expect header\n");
-        return ENOMEM;
-    }
-
-    return EOK;
-}
-
-static errno_t tcurl_set_common_options(struct tcurl_http_state *state,
-                                        struct tevent_req *req)
-{
-    CURLcode crv;
-
-    crv = curl_easy_setopt(state->http_handle,
-                           CURLOPT_HTTPHEADER,
-                           state->curl_headers);
-    if (crv != CURLE_OK) {
-        DEBUG(SSSDBG_OP_FAILURE,
-              "Failed to set HTTP headers [%d]: %s\n",
-              crv, curl_easy_strerror(crv));
-        return EIO;
-    }
-
-    crv = curl_easy_setopt(state->http_handle,
-                           CURLOPT_UNIX_SOCKET_PATH,
-                           state->socket_path);
-    if (crv != CURLE_OK) {
-        DEBUG(SSSDBG_OP_FAILURE,
-              "Failed to set UNIX socket path %s [%d]: %s\n",
-              state->socket_path, crv, curl_easy_strerror(crv));
-        return EIO;
-    }
-
-    crv = curl_easy_setopt(state->http_handle, CURLOPT_URL, state->url);
-    if (crv != CURLE_OK) {
-        DEBUG(SSSDBG_OP_FAILURE,
-              "Failed to set URL %s [%d]: %s\n",
-              state->url, crv, curl_easy_strerror(crv));
-        return EIO;
-    }
-
-    crv = curl_easy_setopt(state->http_handle, CURLOPT_PRIVATE, req);
-    if (crv != CURLE_OK) {
-        DEBUG(SSSDBG_OP_FAILURE,
-              "Failed to set private data [%d]: %s\n",
-              crv, curl_easy_strerror(crv));
-        return EIO;
-    }
-
-    if (state->timeout > 0) {
-        crv = curl_easy_setopt(state->http_handle,
-                               CURLOPT_TIMEOUT,
-                               state->timeout);
-        if (crv != CURLE_OK) {
-            DEBUG(SSSDBG_OP_FAILURE,
-                  "Failed to set timeout [%d]: %s\n",
-                  crv, curl_easy_strerror(crv));
-            return EIO;
-        }
-    }
-
-    return EOK;
-}
-
-static errno_t tcurl_set_write_options(struct tcurl_http_state *state)
-{
-    CURLcode crv;
-
-    crv = curl_easy_setopt(state->http_handle,
-                           CURLOPT_WRITEFUNCTION,
-                           tcurl_http_write_data);
-    if (crv != CURLE_OK) {
-        DEBUG(SSSDBG_OP_FAILURE,
-              "Failed to set write function [%d]: %s\n",
-              crv, curl_easy_strerror(crv));
-        return EIO;
-    }
-
-    crv = curl_easy_setopt(state->http_handle,
-                           CURLOPT_WRITEDATA,
-                           state->outbuf);
-    if (crv != CURLE_OK) {
-        DEBUG(SSSDBG_OP_FAILURE,
-              "Failed to set write data [%d]: %s\n",
-              crv, curl_easy_strerror(crv));
-        return EIO;
-    }
-
-    return EOK;
-}
-
-static errno_t tcurl_set_read_options(struct tcurl_http_state *state)
-{
-    CURLcode crv;
-
-    crv = curl_easy_setopt(state->http_handle,
-                           CURLOPT_READFUNCTION,
-                           tcurl_http_read_data);
-    if (crv != CURLE_OK) {
-        DEBUG(SSSDBG_OP_FAILURE,
-              "Failed to set read function [%d]: %s\n",
-              crv, curl_easy_strerror(crv));
-        return EIO;
-    }
-
-    crv = curl_easy_setopt(state->http_handle,
-                           CURLOPT_READDATA,
-                           state->inbuf);
-    if (crv != CURLE_OK) {
-        DEBUG(SSSDBG_OP_FAILURE,
-              "Failed to set read data [%d]: %s\n",
-              crv, curl_easy_strerror(crv));
-        return EIO;
-    }
-
-    return EOK;
-}
-
-static errno_t tcurl_set_options(struct tcurl_http_state *state,
-                                 struct tevent_req *req,
-                                 enum tcurl_http_request req_type)
-{
-    CURLcode crv;
-    errno_t ret;
-
-    ret = tcurl_set_common_options(state, req);
-    if (ret != EOK) {
-        return ret;
-    }
-
-    ret = tcurl_set_write_options(state);
-    if (ret != EOK) {
-        DEBUG(SSSDBG_CRIT_FAILURE,
-              "Failed to set write callbacks [%d]: %s\n",
-              ret, sss_strerror(ret));
-        return ret;
-    }
-
-    switch (req_type) {
-    case TCURL_HTTP_POST:
-        crv = curl_easy_setopt(state->http_handle,
-                               CURLOPT_CUSTOMREQUEST,
-                               "POST");
-        break;
-    case TCURL_HTTP_PUT:
-        /* CURLOPT_UPLOAD enables HTTP_PUT */
-        crv = curl_easy_setopt(state->http_handle,
-                               CURLOPT_UPLOAD,
-                               1L);
-        if (crv != CURLE_OK) {
-            DEBUG(SSSDBG_OP_FAILURE,
-                  "Failed to set the uplodad option [%d]: %s\n",
-                  crv, curl_easy_strerror(crv));
-            return EIO;
-        }
-
-        /* Causes libcurl to add a sane Content-Length header */
-        crv = curl_easy_setopt(state->http_handle,
-                               CURLOPT_INFILESIZE_LARGE,
-                               (curl_off_t) sss_iobuf_get_size(state->inbuf));
-        if (crv != CURLE_OK) {
-            DEBUG(SSSDBG_OP_FAILURE,
-                  "Failed to set CURLOPT_INFILESIZE_LARGE [%d]: %s\n",
-                  crv, curl_easy_strerror(crv));
-            return EIO;
-        }
-
-        ret = tcurl_set_read_options(state);
-        if (ret != EOK) {
-            DEBUG(SSSDBG_CRIT_FAILURE,
-                  "Failed to set write callbacks [%d]: %s\n",
-                  ret, sss_strerror(ret));
-            return ret;
-        }
-        break;
-    case TCURL_HTTP_GET:
-        /* GET just needs the write callbacks, nothing to do here.. */
-        break;
-    case TCURL_HTTP_DELETE:
-        crv = curl_easy_setopt(state->http_handle,
-                               CURLOPT_CUSTOMREQUEST,
-                               "DELETE");
-        if (crv != CURLE_OK) {
-            DEBUG(SSSDBG_OP_FAILURE,
-                  "Failed to set the uplodad option [%d]: %s\n",
-                  crv, curl_easy_strerror(crv));
-            return EIO;
-        }
-        break;
-    default:
-        return EFAULT;
-    }
-
-    return EOK;
-}
-
-static size_t tcurl_http_write_data(char *ptr,
-                                    size_t size,
-                                    size_t nmemb,
-                                    void *userdata)
+#define tcurl_set_option(tcurl_req, option, value)                          \
+({                                                                          \
+    CURLcode __curl_code;                                                   \
+    errno_t __ret;                                                          \
+                                                                            \
+    __curl_code = curl_easy_setopt((tcurl_req)->curl_easy_handle,           \
+                                   (option), (value));                      \
+    if (__curl_code == CURLE_OK) {                                          \
+        __ret = EOK;                                                        \
+    } else {                                                                \
+        DEBUG(SSSDBG_OP_FAILURE, "Failed to set CURL option %s [%d]: %s\n", \
+              #option, __curl_code, curl_easy_strerror(__curl_code));       \
+        __ret = curl_code2errno(__curl_code);                               \
+    }                                                                       \
+    __ret;                                                                  \
+})
+
+static size_t tcurl_write_data(char *ptr,
+                               size_t size,
+                               size_t nmemb,
+                               void *userdata)
 {
     errno_t ret;
     size_t realsize = size * nmemb;
-    struct sss_iobuf *outbuf = talloc_get_type(userdata, struct sss_iobuf);
+    struct sss_iobuf *outbuf;
+
+    outbuf = talloc_get_type(userdata, struct sss_iobuf);
 
     DEBUG(SSSDBG_TRACE_INTERNAL, "---> begin libcurl data\n");
     DEBUG(SSSDBG_TRACE_INTERNAL, "%s\n", ptr);
     DEBUG(SSSDBG_TRACE_INTERNAL, "<--- end libcurl data\n");
 
-    ret = sss_iobuf_write_len(outbuf, (uint8_t *) ptr, realsize);
+    ret = sss_iobuf_write_len(outbuf, (uint8_t *)ptr, realsize);
     if (ret != EOK) {
-        DEBUG(SSSDBG_CRIT_FAILURE,
-              "Failed to write data to buffer [%d]: %s\n", ret, sss_strerror(ret));
+        DEBUG(SSSDBG_CRIT_FAILURE, "Failed to write data to buffer [%d]: %s\n",
+              ret, sss_strerror(ret));
         /* zero signifies an EOF */
         return 0;
     }
@@ -898,14 +585,16 @@ static size_t tcurl_http_write_data(char *ptr,
     return realsize;
 }
 
-static size_t tcurl_http_read_data(void *ptr,
-                                   size_t size,
-                                   size_t nmemb,
-                                   void *userdata)
+static size_t tcurl_read_data(void *ptr,
+                              size_t size,
+                              size_t nmemb,
+                              void *userdata)
 {
     errno_t ret;
     size_t readbytes;
-    struct sss_iobuf *inbuf = (struct sss_iobuf *) userdata;
+    struct sss_iobuf *inbuf;
+
+    inbuf = talloc_get_type(userdata, struct sss_iobuf);
 
     if (inbuf == NULL) {
         return CURL_READFUNC_ABORT;
@@ -919,22 +608,487 @@ static size_t tcurl_http_read_data(void *ptr,
     return readbytes;
 }
 
-int tcurl_http_recv(TALLOC_CTX *mem_ctx,
-                    struct tevent_req *req,
-                    int *_http_code,
-                    struct sss_iobuf **_outbuf)
+
+struct tcurl_request {
+    CURL *curl_easy_handle;
+
+    struct sss_iobuf *body;
+    struct curl_slist *headers;
+
+    const char *url;
+    const char *socket;
+
+    /* Associated tcurl context if this request is in progress. */
+    struct tcurl_ctx *tcurl_ctx;
+};
+
+struct tcurl_request_state {
+    struct tcurl_request *tcurl_req;
+    struct sss_iobuf *response;
+    int response_code;
+};
+
+struct tevent_req *
+tcurl_request_send(TALLOC_CTX *mem_ctx,
+                   struct tevent_context *ev,
+                   struct tcurl_ctx *tcurl_ctx,
+                   struct tcurl_request *tcurl_req,
+                   long int timeout)
 {
-    struct tcurl_http_state *state = tevent_req_data(req, struct tcurl_http_state);
+    struct tcurl_request_state *state;
+    struct tevent_req *req;
+    CURLMcode curl_code;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct tcurl_request_state);
+    if (req == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n");
+        return NULL;
+    }
+
+    DEBUG(SSSDBG_TRACE_FUNC, "Sending TCURL request for %s, at socket %s\n",
+          tcurl_req->url == NULL ? "<none>" : tcurl_req->url,
+          tcurl_req->socket == NULL ? "<none>" : tcurl_req->socket);
+
+    state->tcurl_req = talloc_steal(state, tcurl_req);
+
+    state->response = sss_iobuf_init_empty(state, TCURL_IOBUF_CHUNK, TCURL_IOBUF_MAX);
+    if (state->response == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = tcurl_set_option(tcurl_req, CURLOPT_PRIVATE, req);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    ret = tcurl_set_option(tcurl_req, CURLOPT_TIMEOUT, timeout);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    ret = tcurl_set_option(tcurl_req, CURLOPT_WRITEFUNCTION, tcurl_write_data);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    ret = tcurl_set_option(tcurl_req, CURLOPT_WRITEDATA, state->response);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    if (tcurl_req->body != NULL) {
+        ret = tcurl_set_option(tcurl_req, CURLOPT_READFUNCTION, tcurl_read_data);
+        if (ret != EOK) {
+            goto done;
+        }
+
+        ret = tcurl_set_option(tcurl_req, CURLOPT_READDATA, tcurl_req->body);
+        if (ret != EOK) {
+            goto done;
+        }
+    }
+
+    curl_code = curl_multi_add_handle(tcurl_ctx->multi_handle,
+                                      tcurl_req->curl_easy_handle);
+    if (curl_code != CURLM_OK) {
+        ret = curlm_code2errno(curl_code);
+        goto done;
+    }
+
+    tcurl_req->tcurl_ctx = tcurl_ctx;
+
+    ret = EAGAIN;
+
+done:
+    if (ret == EOK) {
+        tevent_req_done(req);
+        tevent_req_post(req, ev);
+    } else if (ret != EAGAIN) {
+        tevent_req_error(req, ret);
+        tevent_req_post(req, ev);
+    }
+
+    return req;
+}
+
+static void tcurl_request_done(struct tevent_req *req,
+                               errno_t process_error,
+                               int response_code)
+{
+    struct tcurl_request_state *state;
+
+    DEBUG(SSSDBG_TRACE_FUNC, "TCURL request finished [%d]: %s\n",
+          process_error, sss_strerror(process_error));
+
+    if (req == NULL) {
+        /* To handle case where we fail to obtain request from private data. */
+        DEBUG(SSSDBG_MINOR_FAILURE, "No tevent request provided!\n");
+        return;
+    }
+
+    state = tevent_req_data(req, struct tcurl_request_state);
+
+    curl_multi_remove_handle(state->tcurl_req->tcurl_ctx->multi_handle,
+                             state->tcurl_req->curl_easy_handle);
+
+    /* This request is no longer associated with tcurl context. */
+    state->tcurl_req->tcurl_ctx = NULL;
+
+    if (process_error != EOK) {
+        tevent_req_error(req, process_error);
+        return;
+    }
+
+    state->response_code = response_code;
+
+    tevent_req_done(req);
+    return;
+}
+
+errno_t tcurl_request_recv(TALLOC_CTX *mem_ctx,
+                           struct tevent_req *req,
+                           struct sss_iobuf **_response,
+                           int *_response_code)
+{
+    struct tcurl_request_state *state;
+    state = tevent_req_data(req, struct tcurl_request_state);
 
     TEVENT_REQ_RETURN_ON_ERROR(req);
 
-    if (_http_code != NULL) {
-        *_http_code = state->http_code;
+    if (_response != NULL) {
+        *_response = talloc_steal(mem_ctx, state->response);
     }
 
-    if (_outbuf != NULL) {
-        *_outbuf = talloc_steal(mem_ctx, state->outbuf);
+    if (_response_code != NULL) {
+        *_response_code = state->response_code;
+    }
+
+    return EOK;
+}
+
+static struct curl_slist *
+tcurl_add_header(struct curl_slist *slist, const char *header)
+{
+    struct curl_slist *new;
+
+    new = curl_slist_append(slist, header);
+    if (new == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Cannot add header %s\n", header);
+        if (slist != NULL) {
+            curl_slist_free_all(slist);
+        }
+
+        return NULL;
+    }
+
+    return new;
+}
+
+static errno_t
+tcurl_construct_headers(const char **headers,
+                        struct curl_slist **_slist)
+{
+    struct curl_slist *slist = NULL;
+    int i;
+
+    if (headers == NULL || headers[0] == NULL) {
+        *_slist = NULL;
+        return EOK;
+    }
+
+    for (i = 0; headers[i] != NULL; i++) {
+        slist = tcurl_add_header(slist, headers[i]);
+        if (slist == NULL) {
+            return ENOMEM;
+        }
+    }
+
+    /* Add a dummy header to suppress libcurl adding Expect 100-continue which
+     * was causing libcurl to always wait for the internal timeout when sending
+     * a PUT/POST request because secrets responder does not implement this.
+     */
+    slist = tcurl_add_header(slist, "Expect: ");
+    if (slist == NULL) {
+        return ENOMEM;
+    }
+
+    *_slist = slist;
+
+    return EOK;
+}
+
+static int
+tcurl_request_destructor(struct tcurl_request *tcurl_req)
+{
+    if (tcurl_req->tcurl_ctx != NULL) {
+        DEBUG(SSSDBG_MINOR_FAILURE, "Terminating TCURL request...\n");
+        curl_multi_remove_handle(tcurl_req->tcurl_ctx->multi_handle,
+                                 tcurl_req->curl_easy_handle);
+    }
+
+    if (tcurl_req->headers != NULL) {
+        curl_slist_free_all(tcurl_req->headers);
+    }
+
+    if (tcurl_req->curl_easy_handle != NULL) {
+        curl_easy_cleanup(tcurl_req->curl_easy_handle);
     }
 
     return 0;
 }
+
+static struct tcurl_request *
+tcurl_request_create(TALLOC_CTX *mem_ctx,
+                     const char *socket_path,
+                     const char *url,
+                     const char **headers,
+                     struct sss_iobuf *body)
+{
+    struct tcurl_request *tcurl_req;
+    errno_t ret;
+
+    tcurl_req = talloc_zero(mem_ctx, struct tcurl_request);
+    if (tcurl_req == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n");
+        return NULL;
+    }
+
+    if (url == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "URL cannot be NULL!\n");
+        ret = EINVAL;
+        goto done;
+    }
+
+    /* Setup a curl easy handle. This handle contains state for the request
+     * and is later associated with curl multi handle which performs
+     * asynchronous processing. */
+    tcurl_req->curl_easy_handle = curl_easy_init();
+    if (tcurl_req->curl_easy_handle == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Unable to initialize curl easy handle!\n");
+        ret = ENOMEM;
+        goto done;
+    }
+
+    tcurl_req->url = talloc_strdup(tcurl_req, url);
+    if (tcurl_req->url == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n");
+        ret = ENOMEM;
+        goto done;
+    }
+
+    if (socket_path != NULL) {
+        tcurl_req->socket = talloc_strdup(tcurl_req, socket_path);
+        if (tcurl_req->socket == NULL) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n");
+            ret = ENOMEM;
+            goto done;
+        }
+    }
+
+    ret = tcurl_construct_headers(headers, &tcurl_req->headers);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Unable to construct headers [%d]: %s\n",
+              ret, sss_strerror(ret));
+        ret = ENOMEM;
+        goto done;
+    }
+
+    tcurl_req->body = body;
+
+    talloc_set_destructor(tcurl_req, tcurl_request_destructor);
+
+    ret = tcurl_set_option(tcurl_req, CURLOPT_URL, url);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    if (socket_path != NULL) {
+        ret = tcurl_set_option(tcurl_req, CURLOPT_UNIX_SOCKET_PATH, socket_path);
+        if (ret != EOK) {
+            goto done;
+        }
+    }
+
+    if (body != NULL) {
+        /* Curl will tell the underlying protocol about incoming data length.
+         * In case of HTTP it will add a sane Content-Length header. */
+        ret = tcurl_set_option(tcurl_req, CURLOPT_INFILESIZE_LARGE,
+                               (curl_off_t)sss_iobuf_get_size(body));
+        if (ret != EOK) {
+            goto done;
+        }
+    }
+
+    ret = EOK;
+
+done:
+    if (ret != EOK) {
+        talloc_free(tcurl_req);
+        return NULL;
+    }
+
+    return tcurl_req;
+}
+
+struct tcurl_request *tcurl_http(TALLOC_CTX *mem_ctx,
+                                 enum tcurl_http_method method,
+                                 const char *socket_path,
+                                 const char *url,
+                                 const char **headers,
+                                 struct sss_iobuf *body)
+{
+    struct tcurl_request *tcurl_req;
+    errno_t ret;
+
+    tcurl_req = tcurl_request_create(mem_ctx, socket_path, url, headers, body);
+    if (tcurl_req == NULL) {
+        return NULL;
+    }
+
+    /* Set HTTP specific options. */
+
+    ret = tcurl_set_option(tcurl_req, CURLOPT_HTTPHEADER, tcurl_req->headers);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    switch (method) {
+    case TCURL_HTTP_GET:
+        /* Nothing to do here. GET is default. */
+        break;
+    case TCURL_HTTP_PUT:
+        ret = tcurl_set_option(tcurl_req, CURLOPT_UPLOAD, 1L);
+        if (ret != EOK) {
+            goto done;
+        }
+        break;
+    case TCURL_HTTP_POST:
+        ret = tcurl_set_option(tcurl_req, CURLOPT_CUSTOMREQUEST, "POST");
+        if (ret != EOK) {
+            goto done;
+        }
+        break;
+    case TCURL_HTTP_DELETE:
+        ret = tcurl_set_option(tcurl_req, CURLOPT_CUSTOMREQUEST, "DELETE");
+        if (ret != EOK) {
+            goto done;
+        }
+        break;
+    }
+
+    ret = EOK;
+
+done:
+    if (ret != EOK) {
+        talloc_free(tcurl_req);
+        return NULL;
+    }
+
+    return tcurl_req;
+}
+
+struct tevent_req *tcurl_http_send(TALLOC_CTX *mem_ctx,
+                                   struct tevent_context *ev,
+                                   struct tcurl_ctx *tcurl_ctx,
+                                   enum tcurl_http_method method,
+                                   const char *socket_path,
+                                   const char *url,
+                                   const char **headers,
+                                   struct sss_iobuf *body,
+                                   int timeout)
+{
+    struct tcurl_request *tcurl_req;
+    struct tevent_req *req;
+
+    tcurl_req = tcurl_http(mem_ctx, method, socket_path, url, headers, body);
+    if (tcurl_req == NULL) {
+        return NULL;
+    }
+
+    req = tcurl_request_send(mem_ctx, ev, tcurl_ctx, tcurl_req, timeout);
+    if (req == NULL) {
+        talloc_free(tcurl_req);
+    }
+
+    return req;
+}
+
+errno_t tcurl_http_recv(TALLOC_CTX *mem_ctx,
+                        struct tevent_req *req,
+                        int *_http_code,
+                        struct sss_iobuf **_response)
+{
+    return tcurl_request_recv(mem_ctx, req, _response, _http_code);
+}
+
+errno_t tcurl_req_enable_rawoutput(struct tcurl_request *tcurl_req)
+{
+    return tcurl_set_option(tcurl_req, CURLOPT_HEADER, 1L);
+}
+
+errno_t tcurl_req_verify_peer(struct tcurl_request *tcurl_req,
+                              const char *capath,
+                              const char *cacert,
+                              bool verify_peer,
+                              bool verify_host)
+{
+    errno_t ret;
+
+    long peer = verify_peer ? 1L : 0L;
+    long host = verify_host ? 2L : 0L;
+
+    ret = tcurl_set_option(tcurl_req, CURLOPT_SSL_VERIFYPEER, peer);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    ret = tcurl_set_option(tcurl_req, CURLOPT_SSL_VERIFYHOST, host);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    if (capath != NULL) {
+        ret = tcurl_set_option(tcurl_req, CURLOPT_CAPATH, capath);
+        if (ret != EOK) {
+            return ret;
+        }
+    }
+
+    if (cacert != NULL) {
+        ret = tcurl_set_option(tcurl_req, CURLOPT_CAINFO, cacert);
+        if (ret != EOK) {
+            return ret;
+        }
+    }
+
+    return EOK;
+}
+
+errno_t tcurl_req_set_client_cert(struct tcurl_request *tcurl_req,
+                                  const char *cert,
+                                  const char *key)
+{
+    errno_t ret;
+
+    if (cert == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "You must specify client certificate!\n");
+        return EINVAL;
+    }
+
+    ret = tcurl_set_option(tcurl_req, CURLOPT_SSLCERT, cert);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    if (key != NULL) {
+        /* If client's private key is in separate file. */
+        ret = tcurl_set_option(tcurl_req, CURLOPT_SSLKEY, key);
+        if (ret != EOK) {
+            return ret;
+        }
+    }
+
+    return EOK;
+}
diff --git a/src/util/tev_curl.h b/src/util/tev_curl.h
index 444eb286e09d189b4588e2b2152b5202df3914d8..933abcb9b531412737e8fcf391644d828b125cf8 100644
--- a/src/util/tev_curl.h
+++ b/src/util/tev_curl.h
@@ -27,14 +27,16 @@
 
 #include "util/sss_iobuf.h"
 
+struct tcurl_request;
+
 /**
- * @brief Supported HTTP requests
+ * @brief Supported HTTP methods
  */
-enum tcurl_http_request {
+enum tcurl_http_method {
     TCURL_HTTP_GET,
     TCURL_HTTP_PUT,
-    TCURL_HTTP_DELETE,
     TCURL_HTTP_POST,
+    TCURL_HTTP_DELETE,
 };
 
 /**
@@ -46,16 +48,95 @@ struct tcurl_ctx *tcurl_init(TALLOC_CTX *mem_ctx,
                              struct tevent_context *ev);
 
 /**
+ * @brief Run a single asynchronous TCURL request.
+ *
+ * If the libcurl processing succeeds but we obtain a protocol error we still
+ * mark the tevent request as successful. The protocol error is return from
+ * @tcurl_request_recv as an output parameter.
+ *
+ * @param[in]  mem_ctx      The talloc context that owns the request
+ * @param[in]  ev           Event loop context
+ * @param[in]  tctx         Use tcurl_init to get this context
+ * @param[in]  tcurl_req    TCURL request
+ * @param[in]  timeout      The request timeout in seconds. Use 0 if you want
+ *                          to use the default libcurl timeout.
+ *
+ * @returns A tevent request or NULL on allocation error. On other errors, we
+ * try to set the errno as event error code and run it to completion so that
+ * the programmer can use tcurl_request_recv to read the error code.
+ *
+ * @see tcurl_init
+ * @see tcurl_http
+ * @see tcurl_request_recv
+ */
+struct tevent_req *
+tcurl_request_send(TALLOC_CTX *mem_ctx,
+                   struct tevent_context *ev,
+                   struct tcurl_ctx *tcurl_ctx,
+                   struct tcurl_request *tcurl_req,
+                   long int timeout);
+
+/**
+ * @brief Receive a result of a single asynchronous TCURL request.
+ *
+ * @param[in]  mem_ctx         The talloc context that owns the response
+ * @param[in]  req             The request previously obtained with tcurl_request_send
+ * @param[out] _response       Response to the request
+ * @param[out] _response_code  Protocol response code (may indicate a protocl error)
+ *
+ * @returns The error code of the curl request (not the HTTP code!)
+ */
+errno_t tcurl_request_recv(TALLOC_CTX *mem_ctx,
+                           struct tevent_req *req,
+                           struct sss_iobuf **_response,
+                           int *_response_code);
+
+/**
+ * @brief Create a HTTP request.
+ *
+ * Use this if you need better control over the request options.
+ *
+ * Headers are a NULL-terminated array of strings such as:
+ *   static const char *headers[] = {
+ *       "Content-type: application/octet-stream",
+ *       NULL,
+ *   };
+ *
+ * @param[in]  mem_ctx      The talloc context that owns the tcurl_request
+ * @param[in]  method       TCURL HTTP method
+ * @param[in]  socket_path  The path to the UNIX socket to forward the
+ *                          request to, may be NULL.
+ * @param[in]  url          The request URL, cannot be NULL.
+ * @param[in]  headers      A NULL-terminated array of strings to use
+ *                          as additional HTTP headers. Pass NULL if you
+ *                          don't need any additional headers.
+ * @param[in]  body         The HTTP request input data. For some request
+ *                          types like DELETE, this is OK to leave as NULL.
+ *
+ * @returns A tcurl_request that can be later started with tcurl_request_send
+ * or NULL on error.
+ *
+ * @see tcurl_init
+ * @see tcurl_request_send
+ * @see tcurl_request_recv
+ */
+struct tcurl_request *tcurl_http(TALLOC_CTX *mem_ctx,
+                                 enum tcurl_http_method method,
+                                 const char *socket_path,
+                                 const char *url,
+                                 const char **headers,
+                                 struct sss_iobuf *body);
+
+/**
  * @brief Run a single asynchronous HTTP request.
  *
- * Currently only UNIX sockets at socket_path are supported.
+ * Use this if you do not need control over additional request options.
  *
  * If the request runs into completion, but reports a failure with HTTP return
  * code, the request will be marked as done. Only if the request cannot run at
  * all (if e.g. the socket is unreachable), the request will fail completely.
  *
- * Headers are a NULL-terminated
- * array of strings such as:
+ * Headers are a NULL-terminated array of strings such as:
  *   static const char *headers[] = {
  *       "Content-type: application/octet-stream",
  *       NULL,
@@ -63,15 +144,15 @@ struct tcurl_ctx *tcurl_init(TALLOC_CTX *mem_ctx,
  *
  * @param[in]  mem_ctx      The talloc context that owns the iobuf
  * @param[in]  ev           Event loop context
- * @param[in]  tctx         Use tcurl_init to get this context
- * @param[in]  req_type     The request type
+ * @param[in]  tcurl_ctx    Use tcurl_init to get this context
+ * @param[in]  method       HTTP method
  * @param[in]  socket_path  The path to the UNIX socket to forward the
- *                          request to
- * @param[in]  url          The request URL
+ *                          request to, may be NULL.
+ * @param[in]  url          The request URL, cannot be NULL.
  * @param[in]  headers      A NULL-terminated array of strings to use
  *                          as additional HTTP headers. Pass NULL if you
  *                          don't need any additional headers.
- * @param[in]  req_data     The HTTP request input data. For some request
+ * @param[in]  body         The HTTP request input data. For some request
  *                          types like DELETE, this is OK to leave as NULL.
  * @param[in]  timeout      The request timeout in seconds. Use 0 if you want
  *                          to use the default libcurl timeout.
@@ -85,12 +166,12 @@ struct tcurl_ctx *tcurl_init(TALLOC_CTX *mem_ctx,
  */
 struct tevent_req *tcurl_http_send(TALLOC_CTX *mem_ctx,
                                    struct tevent_context *ev,
-                                   struct tcurl_ctx *tctx,
-                                   enum tcurl_http_request req_type,
+                                   struct tcurl_ctx *tcurl_ctx,
+                                   enum tcurl_http_method method,
                                    const char *socket_path,
                                    const char *url,
-                                   const char *headers[],
-                                   struct sss_iobuf *req_data,
+                                   const char **headers,
+                                   struct sss_iobuf *body,
                                    int timeout);
 
 /**
@@ -104,9 +185,62 @@ struct tevent_req *tcurl_http_send(TALLOC_CTX *mem_ctx,
  *
  * @returns The error code of the curl request (not the HTTP code!)
  */
-int tcurl_http_recv(TALLOC_CTX *mem_ctx,
-                    struct tevent_req *req,
-                    int *_http_code,
-                    struct sss_iobuf **_outbuf);
+errno_t tcurl_http_recv(TALLOC_CTX *mem_ctx,
+                        struct tevent_req *req,
+                        int *_http_code,
+                        struct sss_iobuf **_response);
+
+/**
+ * @brief We are usually interested only in the reply body without protocol
+ * headers. Call this function on tcurl_request, if you want to include
+ * complete protocol response in the output buffer.
+ *
+ * @param[in]  tcurl_request
+ *
+ * @returns errno code
+ *
+ * @see tcurl_http
+ */
+errno_t tcurl_req_enable_rawoutput(struct tcurl_request *tcurl_req);
+
+/**
+ * @brief TLS is enabled automatically by providing an URL that points to
+ * TLS-enabled protocol such as https. If you want to provide different
+ * path to CA directory or disable peer/hostname check explicitly, use
+ * this function on tcurl_request.
+ *
+ * @param[in]  tcurl_request
+ * @param[in]  capath        Path to directory containing installed CA certificates.
+ *                           If not set, libcurl default is used.
+ * @param[ing  cacert        CA certificate. If NULL it is found in @capath.
+ * @param[in]  verify_peer   If false, the peer certificate is not verified.
+ * @param[in]  verify_host   If false, the host name provided in remote
+ *                           certificate may differ from the actual host name.
+ *
+ * @returns errno code
+ *
+ * @see tcurl_http
+ */
+errno_t tcurl_req_verify_peer(struct tcurl_request *tcurl_req,
+                              const char *capath,
+                              const char *cacert,
+                              bool verify_peer,
+                              bool verify_host);
+/**
+ * @brief Some server require client verification during TLS setup. You can
+ * provide path to client's certificate file. If this file does not contain
+ * private key, you can specify a different file the holds the private key.
+ *
+ * @param[in]  tcurl_request
+ * @param[in]  cert          Path to client's certificate.
+ * @param[in]  key           Path to client's private key.
+ *
+ * @returns errno code
+ *
+ * @see tcurl_http
+ */
+errno_t tcurl_req_set_client_cert(struct tcurl_request *tcurl_req,
+                                  const char *cert,
+                                  const char *key);
 
 #endif /* __TEV_CURL_H */
diff --git a/src/util/util_errors.c b/src/util/util_errors.c
index 60c2f439b3e39b1dbff353e429114cb5a3070052..466a3b4062f39b29d831a5d8a62dc8d576eb2e97 100644
--- a/src/util/util_errors.c
+++ b/src/util/util_errors.c
@@ -111,6 +111,10 @@ struct err_string error_to_str[] = {
     { "Credential cache name not allowed" }, /* ERR_KCM_WRONG_CCNAME_FORMAT */
     { "Cannot encode a JSON object to string" }, /* ERR_JSON_ENCODING */
     { "Cannot decode a JSON object from string" }, /* ERR_JSON_DECODING */
+    { "Invalid certificate provided" }, /* ERR_INVALID_CERT */
+    { "Unable to initialize SSL" }, /* ERR_SSL_FAILURE */
+    { "Unable to verify peer" }, /* ERR_UNABLE_TO_VERIFY_PEER */
+    { "Unable to resolve host" }, /* ERR_UNABLE_TO_RESOLVE_HOST */
     { "ERR_LAST" } /* ERR_LAST */
 };
 
diff --git a/src/util/util_errors.h b/src/util/util_errors.h
index 4e9da814702e2cd46edc52fd5c2ae5f640602609..2f90c0a5d65325a431a8e4d9a480170808c9198e 100644
--- a/src/util/util_errors.h
+++ b/src/util/util_errors.h
@@ -133,6 +133,10 @@ enum sssd_errors {
     ERR_KCM_WRONG_CCNAME_FORMAT,
     ERR_JSON_ENCODING,
     ERR_JSON_DECODING,
+    ERR_INVALID_CERT,
+    ERR_SSL_FAILURE,
+    ERR_UNABLE_TO_VERIFY_PEER,
+    ERR_UNABLE_TO_RESOLVE_HOST,
     ERR_LAST            /* ALWAYS LAST */
 };
 
-- 
2.9.3