387014f928
required for building freeipa-4.5.x in rawhide
1478 lines
48 KiB
Diff
1478 lines
48 KiB
Diff
From 300b9e9217ee1ed8d845ed2370c5ccf5c87afb36 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 77/97] 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>
|
|
---
|
|
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.12.2
|
|
|