/*
SSSD
libcurl tevent integration
Copyright (C) Red Hat, 2016
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>
#include <errno.h>
#include <talloc.h>
#include <tevent.h>
#include <curl/curl.h>
#include "util/util.h"
#include "util/tev_curl.h"
#define TCURL_IOBUF_CHUNK 1024
/* This limit in the same one as KCM_REPLY_MAX */
#define TCURL_IOBUF_MAX 10*1024*1024
static bool global_is_curl_initialized;
/**
* @brief The main structure of the tcurl module.
*
* Use tcurl_init() to initialize it, then pass to the request.
* Should be kept opaque in the future.
*
* @see tcurl_init()
*/
struct tcurl_ctx {
struct tevent_context *ev;
/* See where we set CURLMOPT_TIMERFUNCTION */
struct tevent_timer *process_timer;
/* Since we want the API to be non-blocking, all the transfers use
* the curl's multi interface:
* https://ec.haxx.se/libcurl-drive-multi.html
* and then each transfer also uses an easy interface instance for
* the transfer's private data
*/
CURLM *multi_handle;
};
/**
* @brief A tevent wrapper around curl socket
*/
struct tcurl_sock {
struct tcurl_ctx *tctx; /* Backchannel to the main context */
curl_socket_t sockfd; /* curl socket is an int typedef on UNIX */
struct tevent_fd *fde; /* tevent tracker of the fd events */
};
static void tcurl_request_done(struct tevent_req *req,
errno_t process_error,
int response_code);
static errno_t curl_code2errno(CURLcode crv)
{
switch (crv) {
/* HTTP error does not fail the whole request, just returns the error
* separately
*/
case CURLE_HTTP_RETURNED_ERROR:
case CURLE_OK:
return EOK;
case CURLE_URL_MALFORMAT:
return EBADMSG;
case CURLE_COULDNT_CONNECT:
return EHOSTUNREACH;
case CURLE_REMOTE_ACCESS_DENIED:
return EACCES;
case CURLE_OUT_OF_MEMORY:
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;
}
return EIO;
}
static errno_t tcurl_global_init(void)
{
errno_t ret;
if (global_is_curl_initialized == false) {
ret = curl_global_init(CURL_GLOBAL_ALL);
if (ret != CURLE_OK) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Cannot initialize global curl options [%d]\n", ret);
return EIO;
}
}
global_is_curl_initialized = true;
return EOK;
}
static int curl2tev_flags(int curlflags)
{
int flags = 0;
switch (curlflags) {
case CURL_POLL_IN:
flags |= TEVENT_FD_READ;
break;
case CURL_POLL_OUT:
flags |= TEVENT_FD_WRITE;
break;
case CURL_POLL_INOUT:
flags |= (TEVENT_FD_READ | TEVENT_FD_WRITE);
break;
}
return flags;
}
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;
easy_handle = message->easy_handle;
if (easy_handle == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "BUG: NULL handle for message %p\n", message);
return;
}
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));
/* not fatal since we need this only for debugging */
} else {
DEBUG(SSSDBG_TRACE_FUNC, "Handled %s\n", done_url);
}
}
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",
crv, curl_easy_strerror(crv));
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));
goto done;
}
/* 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 response code\n");
ret = curl_code2errno(crv);
goto done;
}
ret = EOK;
done:
tcurl_request_done(req, ret, response_code);
}
static void process_curl_activity(struct tcurl_ctx *tctx)
{
CURLMsg *message;
int pending;
while ((message = curl_multi_info_read(tctx->multi_handle, &pending))) {
switch (message->msg) {
case CURLMSG_DONE:
handle_curlmsg_done(message);
break;
default:
DEBUG(SSSDBG_TRACE_LIBS,
"noop for curl msg %d\n", message->msg);
break;
}
}
}
static void tcurlsock_input_available(struct tevent_context *ev,
struct tevent_fd *fde,
uint16_t flags,
void *data)
{
struct tcurl_ctx *tctx;
struct tcurl_sock *tcs = NULL;
int curl_flags = 0;
int running_handles;
tcs = talloc_get_type(data, struct tcurl_sock);
if (tcs == NULL) {
return;
}
if (flags & TEVENT_FD_READ) {
curl_flags |= CURL_CSELECT_IN;
}
if (flags & TEVENT_FD_WRITE) {
curl_flags |= CURL_CSELECT_OUT;
}
/* multi_socket_action might invalidate tcs when the transfer ends,
* so we need to store tctx separately
*/
tctx = tcs->tctx;
/* https://ec.haxx.se/libcurl-drive-multi-socket.html */
curl_multi_socket_action(tcs->tctx->multi_handle,
tcs->sockfd,
curl_flags,
&running_handles);
process_curl_activity(tctx);
}
/**
* @brief Registers a curl's socket with tevent
*
* Creates a private structure, registers the socket with tevent and finally
* registers the tcurl_sock structure as a private pointer for the curl
* socket for later
*/
static struct tcurl_sock *register_curl_socket(struct tcurl_ctx *tctx,
curl_socket_t sockfd,
int flags)
{
struct tcurl_sock *tcs;
tcs = talloc_zero(tctx, struct tcurl_sock);
if (tcs == NULL) {
return NULL;
}
tcs->sockfd = sockfd;
tcs->tctx = tctx;
tcs->fde = tevent_add_fd(tctx->ev, tcs, sockfd, flags,
tcurlsock_input_available, tcs);
if (tcs->fde == NULL) {
talloc_free(tcs);
return NULL;
}
curl_multi_assign(tctx->multi_handle, sockfd, (void *) tcs);
return tcs;
}
/* libcurl informs the application about socket activity to wait for with
* this callback */
static int handle_socket(CURL *easy,
curl_socket_t s,
int action,
void *userp,
void *socketp)
{
struct tcurl_ctx *tctx = NULL;
struct tcurl_sock *tcsock;
int flags = 0;
tctx = talloc_get_type(userp, struct tcurl_ctx);
if (tctx == NULL) {
return 1;
}
DEBUG(SSSDBG_TRACE_INTERNAL,
"Activity on curl socket %d socket data %p\n", s, socketp);
switch (action) {
case CURL_POLL_IN:
case CURL_POLL_OUT:
case CURL_POLL_INOUT:
/* There is some activity on a socket */
flags = curl2tev_flags(action);
if (socketp == NULL) {
/* If this socket doesn't have private data, it must be a new one,
* let's start tracking it with tevent
*/
tcsock = register_curl_socket(tctx, s, flags);
if (tcsock == NULL) {
return 1;
}
} else {
/* If we are already tracking this socket, just set the correct
* flags for tevent and pass the control to tevent
*/
tcsock = talloc_get_type(socketp, struct tcurl_sock);
if (tcsock == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE,
"BUG: No private data for socket %d\n", s);
return 1;
}
tevent_fd_set_flags(tcsock->fde, flags);
}
break;
case CURL_POLL_REMOVE:
/* This socket is being closed by curl, so we need to.. */
tcsock = talloc_get_type(socketp, struct tcurl_sock);
if (tcsock == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE,
"BUG: Trying to remove an untracked socket %d\n", s);
}
/* ..stop tracking the socket with the multi handle.. */
curl_multi_assign(tctx->multi_handle, s, NULL);
/* ..and stop tracking the fd with tevent */
talloc_free(tcsock);
break;
default:
return 1;
}
return 0;
}
static void check_curl_timeouts(struct tcurl_ctx *tctx)
{
int running_handles;
curl_multi_socket_action(tctx->multi_handle,
CURL_SOCKET_TIMEOUT,
0,
&running_handles);
DEBUG(SSSDBG_TRACE_ALL,
"Still tracking %d outstanding requests\n", running_handles);
/* https://ec.haxx.se/libcurl-drive-multi-socket.html */
process_curl_activity(tctx);
}
static void check_fd_activity(struct tevent_context *ev,
struct tevent_timer *te,
struct timeval current_time,
void *private_data)
{
struct tcurl_ctx *tctx = talloc_get_type(private_data, struct tcurl_ctx);
check_curl_timeouts(tctx);
}
static int schedule_fd_processing(CURLM *multi,
long timeout_ms,
void *userp)
{
struct timeval tv = { 0, 0 };
struct tcurl_ctx *tctx = talloc_get_type(userp, struct tcurl_ctx);
DEBUG(SSSDBG_TRACE_INTERNAL, "timeout_ms: %ld\n", timeout_ms);
if (timeout_ms == -1) {
/* man curlmopt_timerfunction(3) says:
* A timeout_ms value of -1 means you should delete your timer.
*/
talloc_zfree(tctx->process_timer);
check_curl_timeouts(tctx);
return 0;
}
tv = tevent_timeval_current_ofs(0, timeout_ms * 1000);
/* There is only one timer per multi handle, so it makes sense to cancel
* the previous one.
*
* From https://ec.haxx.se/libcurl-drive-multi-socket.html:
* There is only one timeout for the application to handle for the
* entire multi handle, no matter how many individual easy handles
* that have been added or transfers that are in progress. The timer
* callback will be updated with the current nearest-in-time period to
* wait.
*/
talloc_zfree(tctx->process_timer);
tctx->process_timer = tevent_add_timer(tctx->ev, tctx, tv,
check_fd_activity, tctx);
if (tctx->process_timer == NULL) {
return -1;
}
return 0;
}
static int tcurl_ctx_destroy(struct tcurl_ctx *ctx)
{
if (ctx == NULL) {
return 0;
}
curl_multi_cleanup(ctx->multi_handle);
return 0;
}
struct tcurl_ctx *tcurl_init(TALLOC_CTX *mem_ctx,
struct tevent_context *ev)
{
errno_t ret;
struct tcurl_ctx *tctx = NULL;
CURLMcode cmret;
/* Per the manpage it is safe to call the initialization multiple
* times, as long as this is done before any other curl calls to
* make sure we don't mangle the global curl environment
*/
ret = tcurl_global_init();
if (ret != EOK) {
goto fail;
}
tctx = talloc_zero(mem_ctx, struct tcurl_ctx);
if (tctx == NULL) {
goto fail;
}
tctx->ev = ev;
tctx->multi_handle = curl_multi_init();
if (tctx->multi_handle == NULL) {
goto fail;
}
talloc_set_destructor(tctx, tcurl_ctx_destroy);
cmret = curl_multi_setopt(tctx->multi_handle,
CURLMOPT_SOCKETDATA, tctx);
if (cmret != CURLM_OK) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Cannot set CURLMOPT_SOCKETDATA [%d]: %s\n",
cmret, curl_multi_strerror(cmret));
goto fail;
}
/*
* When there is some activity on a socket associated with the multi
* handle, then the handle_socket() function will be called with the
* global context as private data
*/
cmret = curl_multi_setopt(tctx->multi_handle,
CURLMOPT_SOCKETFUNCTION, handle_socket);
if (cmret != CURLM_OK) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Cannot set CURLMOPT_SOCKETFUNCTION [%d]: %s\n",
cmret, curl_multi_strerror(cmret));
goto fail;
}
/* When integrated in a mainloop, the curl multi interface must
* kick off the communication in another eventloop tick. Similar
* to the handle_socet function, the tcurl context is passed in
* as private data
*/
cmret = curl_multi_setopt(tctx->multi_handle,
CURLMOPT_TIMERFUNCTION, schedule_fd_processing);
if (cmret != CURLM_OK) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Cannot set CURLMOPT_TIMERFUNCTION [%d]: %s\n",
cmret, curl_multi_strerror(cmret));
goto fail;
}
cmret = curl_multi_setopt(tctx->multi_handle, CURLMOPT_TIMERDATA, tctx);
if (cmret != CURLM_OK) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Cannot set CURLMOPT_TIMERDATA [%d]: %s\n",
cmret, curl_multi_strerror(cmret));
}
return tctx;
fail:
talloc_free(tctx);
return NULL;
}
#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;
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);
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE, "Failed to write data to buffer [%d]: %s\n",
ret, sss_strerror(ret));
/* zero signifies an EOF */
return 0;
}
return realsize;
}
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;
inbuf = talloc_get_type(userdata, struct sss_iobuf);
if (inbuf == NULL) {
return CURL_READFUNC_ABORT;
}
ret = sss_iobuf_read(inbuf, size * nmemb, ptr, &readbytes);
if (ret != EOK) {
return CURL_READFUNC_ABORT;
}
return readbytes;
}
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_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 (_response != NULL) {
*_response = talloc_steal(mem_ctx, state->response);
}
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;
}
errno_t tcurl_req_http_basic_auth(struct tcurl_request *tcurl_req,
const char *username,
const char *password)
{
errno_t ret;
ret = tcurl_set_option(tcurl_req, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
if (ret != EOK) {
return ret;
}
ret = tcurl_set_option(tcurl_req, CURLOPT_USERNAME, username);
if (ret != EOK) {
return ret;
}
ret = tcurl_set_option(tcurl_req, CURLOPT_PASSWORD, password);
if (ret != EOK) {
return ret;
}
return EOK;
}