URL: https://github.com/SSSD/sssd/pull/152
Author: jhrozek
 Title: #152: Add a tevent wrapper around libcurl's asynchronous interface
Action: opened

PR body:
"""
This patch set adds a generic utility module that uses libcurl's multi
interface to allow the caller to talk HTTP using libcurl. The patch set
was part of the KCM responder branch, but as it might be useful to unblock
pbrezina's development, I'm submitting it separately.

The iobuf module might seem like an overkill for the libcurl tevent wrapper,
but please also check the KCM branch during review -- there it proved
quite valuable, so I think it makes sense to just reuse the same code in
the curl wrapper as well.
"""

To pull the PR as Git branch:
git remote add ghsssd https://github.com/SSSD/sssd
git fetch ghsssd pull/152/head:pr152
git checkout pr152
From 80f078999f494810f07d47095b031b89fe3b9598 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhro...@redhat.com>
Date: Fri, 23 Sep 2016 13:41:53 +0200
Subject: [PATCH 1/5] UTIL: Add a new macro SAFEALIGN_MEMCPY_CHECK

We will use it later in the KCM server
---
 src/util/util_safealign.h | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/util/util_safealign.h b/src/util/util_safealign.h
index a2cd4dd..0d9a579 100644
--- a/src/util/util_safealign.h
+++ b/src/util/util_safealign.h
@@ -124,6 +124,12 @@ safealign_memcpy(void *dest, const void *src, size_t n, size_t *counter)
     safealign_memcpy(dest, CV_MACRO_val, sizeof(char) * length, pctr); \
 } while(0)
 
+#define SAFEALIGN_MEMCPY_CHECK(dest, src, srclen, len, pctr) do { \
+    if ((*(pctr) + srclen) > (len) || \
+        SIZE_T_OVERFLOW(*(pctr), srclen)) { return EINVAL; } \
+    safealign_memcpy(dest, src, srclen, pctr); \
+} while(0)
+
 /* Aliases for backward compatibility. */
 #define SAFEALIGN_SET_VALUE SAFEALIGN_SETMEM_VALUE
 #define SAFEALIGN_SET_INT64 SAFEALIGN_SETMEM_INT64

From 23ab0206f7b7d78984fd9058504cbd30e1a9cfa1 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhro...@redhat.com>
Date: Tue, 20 Sep 2016 18:46:40 +0200
Subject: [PATCH 2/5] UTIL: Add a generic iobuf module

The KCM responder reads bytes and writes bytes from a buffer of bytes.
Instead of letting the caller deal with low-level handling using the
SAFEALIGN macros, this patch adds a new iobuf.c module with more
high-level functions.

The core is a iobuf struct that keeps track of the buffer, its total
capacity and a current read or write position.

There are helper function to read or write a generic buffer with a set
length. Later, we will also add convenience functions to read C data
types using the SAFEALIGN macros.
---
 Makefile.am                   |  22 ++++++
 src/tests/cmocka/test_iobuf.c | 157 ++++++++++++++++++++++++++++++++++++++++++
 src/util/sss_iobuf.c          | 157 ++++++++++++++++++++++++++++++++++++++++++
 src/util/sss_iobuf.h          |  45 ++++++++++++
 4 files changed, 381 insertions(+)
 create mode 100644 src/tests/cmocka/test_iobuf.c
 create mode 100644 src/util/sss_iobuf.c
 create mode 100644 src/util/sss_iobuf.h

diff --git a/Makefile.am b/Makefile.am
index 2304b39..835f641 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -274,6 +274,7 @@ if HAVE_CMOCKA
         test_ipa_dn \
         simple-access-tests \
         krb5_common_test \
+        test_iobuf \
         $(NULL)
 
 if HAVE_LIBRESOLV
@@ -634,6 +635,7 @@ dist_noinst_HEADERS = \
     src/util/util_safealign.h \
     src/util/util_sss_idmap.h \
     src/util/util_creds.h \
+    src/util/sss_iobuf.h \
     src/monitor/monitor.h \
     src/monitor/monitor_interfaces.h \
     src/monitor/monitor_iface_generated.h \
@@ -1793,6 +1795,7 @@ krb5_utils_tests_SOURCES = \
     src/providers/krb5/krb5_common.c \
     src/providers/krb5/krb5_opts.c \
     src/util/sss_krb5.c \
+    src/util/sss_iobuf.c \
     src/providers/data_provider_fo.c \
     src/providers/data_provider_opts.c \
     src/providers/data_provider_callbacks.c \
@@ -2064,6 +2067,7 @@ krb5_child_test_SOURCES = \
     src/providers/krb5/krb5_common.c \
     src/providers/krb5/krb5_opts.c \
     src/util/sss_krb5.c \
+    src/util/sss_iobuf.c \
     src/providers/data_provider_fo.c \
     src/providers/data_provider_opts.c \
     src/providers/data_provider_callbacks.c \
@@ -2733,6 +2737,7 @@ test_copy_ccache_SOURCES = \
     src/tests/cmocka/test_copy_ccache.c \
     src/providers/krb5/krb5_ccache.c \
     src/util/sss_krb5.c \
+    src/util/sss_iobuf.c \
     $(NULL)
 test_copy_ccache_CFLAGS = \
     $(AM_CFLAGS) \
@@ -2751,6 +2756,7 @@ test_copy_keytab_SOURCES = \
     src/tests/cmocka/test_copy_keytab.c \
     src/providers/krb5/krb5_keytab.c \
     src/util/sss_krb5.c \
+    src/util/sss_iobuf.c \
     $(NULL)
 test_copy_keytab_CFLAGS = \
     $(AM_CFLAGS) \
@@ -3113,6 +3119,19 @@ test_ipa_dn_LDADD = \
     libsss_test_common.la \
     $(NULL)
 
+test_iobuf_SOURCES = \
+    src/util/sss_iobuf.c \
+    src/tests/cmocka/test_iobuf.c \
+    $(NULL)
+test_iobuf_CFLAGS = \
+    $(AM_CFLAGS) \
+    $(NULL)
+test_iobuf_LDADD = \
+    $(CMOCKA_LIBS) \
+    $(SSSD_LIBS) \
+    $(NULL)
+
+
 EXTRA_simple_access_tests_DEPENDENCIES = \
     $(ldblib_LTLIBRARIES)
 simple_access_tests_SOURCES = \
@@ -3429,6 +3448,7 @@ libsss_krb5_common_la_SOURCES = \
     src/providers/krb5/krb5_init_shared.c \
     src/providers/krb5/krb5_ccache.c \
     src/util/sss_krb5.c \
+    src/util/sss_iobuf.c \
     src/util/become_user.c \
     $(NULL)
 libsss_krb5_common_la_CFLAGS = \
@@ -3641,6 +3661,7 @@ krb5_child_SOURCES = \
     src/providers/dp_pam_data_util.c \
     src/util/user_info_msg.c \
     src/util/sss_krb5.c \
+    src/util/sss_iobuf.c \
     src/util/find_uid.c \
     src/util/atomic_io.c \
     src/util/authtok.c \
@@ -3674,6 +3695,7 @@ ldap_child_SOURCES = \
     src/providers/ldap/ldap_child.c \
     src/providers/krb5/krb5_keytab.c \
     src/util/sss_krb5.c \
+    src/util/sss_iobuf.c \
     src/util/atomic_io.c \
     src/util/authtok.c \
     src/util/authtok-utils.c \
diff --git a/src/tests/cmocka/test_iobuf.c b/src/tests/cmocka/test_iobuf.c
new file mode 100644
index 0000000..be56909
--- /dev/null
+++ b/src/tests/cmocka/test_iobuf.c
@@ -0,0 +1,157 @@
+/*
+    SSSD
+
+    test_iobuf - IO buffer tests
+
+    Copyright (C) 2016 Red Hat
+
+    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 <fcntl.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include "util/sss_iobuf.h"
+
+static void test_sss_iobuf_read(void **state)
+{
+    errno_t ret;
+    uint8_t buffer[] = { 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0 };
+    uint8_t readbuf[64] = { 0 };
+    size_t nread;
+    struct sss_iobuf ib;
+
+    ret = sss_iobuf_init(&ib, buffer, sizeof(buffer));
+    assert_int_equal(ret, EOK);
+
+    ret = sss_iobuf_read(&ib, 5, readbuf, &nread);
+    assert_int_equal(ret, EOK);
+    /* There is enough data in the buffer */
+    assert_int_equal(nread, 5);
+    /* The data matches beginning of the buffer */
+    assert_int_equal(strncmp((const char *) readbuf, "Hello", 5), 0);
+
+    memset(readbuf, 0, sizeof(readbuf));
+    ret = sss_iobuf_read(&ib, 3, readbuf, &nread);
+    assert_int_equal(ret, EOK);
+    /* There is enough data in the buffer */
+    assert_int_equal(nread, 3);
+    /* The data matches beginning of the buffer */
+    assert_int_equal(strncmp((const char *) readbuf, " wo", 3), 0);
+
+    /* Try to read more than the buffer has */
+    memset(readbuf, 0, sizeof(readbuf));
+    ret = sss_iobuf_read(&ib, 10, readbuf, &nread);
+    /* This is not a fatal error */
+    assert_int_equal(ret, EOK);
+    /* We just see how much there was */
+    assert_int_equal(nread, 4);
+    /* And get the rest of the buffer back. readbuf includes trailing zero now */
+    assert_int_equal(strcmp((const char *) readbuf, "rld"), 0);
+
+    /* Reading a depleted buffer will just yield zero bytes read now */
+    ret = sss_iobuf_read(&ib, 10, readbuf, &nread);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(nread, 0);
+
+    /* Failure cases */
+    ret = sss_iobuf_read(NULL, 10, readbuf, &nread);
+    assert_int_equal(ret, EINVAL);
+    ret = sss_iobuf_read(&ib, 10, NULL, &nread);
+    assert_int_equal(ret, EINVAL);
+}
+
+static void test_sss_iobuf_write(void **state)
+{
+    struct sss_iobuf *ib;
+    size_t hwlen = sizeof("Hello world");   /* Includes trailing zero */
+    uint8_t readbuf[64];
+    size_t nread;
+    errno_t ret;
+
+    /* Exactly fill the capacity */
+    ib = sss_iobuf_tc(NULL, hwlen, hwlen);
+    assert_non_null(ib);
+    ret = sss_iobuf_write_len(ib,
+                              (uint8_t *) discard_const("Hello world"),
+                              sizeof("Hello world"));
+    assert_int_equal(ret, EOK);
+
+    sss_iobuf_rewind(ib);
+    ret = sss_iobuf_read(ib, sizeof(readbuf), readbuf, &nread);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(nread, hwlen);
+    assert_int_equal(strcmp((const char *) readbuf, "Hello world"), 0);
+    talloc_zfree(ib);
+
+    /* Overflow the capacity by one */
+    ib = sss_iobuf_tc(NULL, hwlen, hwlen);
+    assert_non_null(ib);
+    ret = sss_iobuf_write_len(ib,
+                              (uint8_t *) discard_const("Hello world!"),
+                              sizeof("Hello world!"));
+    assert_int_not_equal(ret, EOK);
+    talloc_zfree(ib);
+
+    /* Test resizing exactly up to capacity in several writes */
+    ib = sss_iobuf_tc(NULL, 2, hwlen);
+    assert_non_null(ib);
+
+    ret = sss_iobuf_write_len(ib,
+                              (uint8_t *) discard_const("Hello "),
+                              sizeof("Hello ")-1); /* Not the null byte now.. */
+    assert_int_equal(ret, EOK);
+    ret = sss_iobuf_write_len(ib,
+                              (uint8_t *) discard_const("world"),
+                              sizeof("world"));
+    assert_int_equal(ret, EOK);
+
+    sss_iobuf_rewind(ib);
+    ret = sss_iobuf_read(ib, sizeof(readbuf), readbuf, &nread);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(nread, hwlen);
+    talloc_zfree(ib);
+    assert_int_equal(strcmp((const char *) readbuf, "Hello world"), 0);
+    talloc_zfree(ib);
+
+    /* Overflow the capacity during a resize by one */
+    ib = sss_iobuf_tc(NULL, 2, hwlen);
+    assert_non_null(ib);
+
+    ret = sss_iobuf_write_len(ib,
+                              (uint8_t *) discard_const("Hello "),
+                              sizeof("Hello ")-1); /* Not the null byte now.. */
+    assert_int_equal(ret, EOK);
+    ret = sss_iobuf_write_len(ib,
+                              (uint8_t *) discard_const("world!"),
+                              sizeof("world!"));
+    assert_int_not_equal(ret, EOK);
+    talloc_zfree(ib);
+}
+
+int main(void)
+{
+    const struct CMUnitTest tests[] = {
+        cmocka_unit_test(test_sss_iobuf_read),
+        cmocka_unit_test(test_sss_iobuf_write),
+    };
+
+    return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/src/util/sss_iobuf.c b/src/util/sss_iobuf.c
new file mode 100644
index 0000000..2469475
--- /dev/null
+++ b/src/util/sss_iobuf.c
@@ -0,0 +1,157 @@
+#include <talloc.h>
+
+#include "util/util.h"
+#include "util/sss_iobuf.h"
+
+static void iobuf_init(struct sss_iobuf *iobuf,
+                       uint8_t *data,
+                       size_t size,
+                       size_t capacity)
+{
+    iobuf->data = data;
+    iobuf->size = size;
+    iobuf->capacity = capacity;
+    iobuf->dp = 0;
+}
+
+struct sss_iobuf *sss_iobuf_tc(TALLOC_CTX *mem_ctx,
+                               size_t size,
+                               size_t capacity)
+{
+    struct sss_iobuf *iobuf;
+    uint8_t *buf;
+
+    iobuf = talloc_zero(mem_ctx, struct sss_iobuf);
+    if (iobuf == NULL) {
+        return NULL;
+    }
+
+    buf = talloc_zero_array(iobuf, uint8_t, size);
+    if (buf == NULL) {
+        talloc_free(iobuf);
+        return NULL;
+    }
+
+    iobuf_init(iobuf, buf, size, capacity);
+    return iobuf;
+}
+
+errno_t sss_iobuf_init(struct sss_iobuf *iobuf,
+                       uint8_t *data,
+                       size_t size)
+{
+    if (iobuf == NULL || data == NULL) {
+        return EINVAL;
+    }
+
+    iobuf_init(iobuf, data, size,
+               size); /* These buffers don't extend */
+    return EOK;
+}
+
+static size_t sss_iobuf_get_len(struct sss_iobuf *iobuf)
+{
+    if (iobuf == NULL) {
+        return 0;
+    }
+
+    return (iobuf->size - iobuf->dp);
+}
+
+static errno_t ensure_bytes(struct sss_iobuf *iobuf,
+                            size_t nbytes)
+{
+    size_t wantsize;
+    size_t newsize;
+    uint8_t *newdata;
+
+    if (iobuf == NULL) {
+        return EINVAL;
+    }
+
+    wantsize = iobuf->dp + nbytes;
+    if (wantsize <= iobuf->size) {
+        /* Enough space already */
+        return EOK;
+    }
+
+    /* Else, try to extend the iobuf */
+    if (wantsize > iobuf->capacity) {
+        /* We will never grow past capacity */
+        return ENOBUFS;
+    }
+
+    /* Double the size until we add at least nbytes, but stop if we double past capacity */
+    for (newsize = iobuf->size;
+         (newsize < wantsize) && (newsize < iobuf->capacity);
+         newsize *= 2);
+
+    if (newsize > iobuf->capacity) {
+        newsize = iobuf->capacity;
+    }
+
+    newdata = talloc_realloc(iobuf, iobuf->data, uint8_t, newsize);
+    if (newdata == NULL) {
+        return ENOMEM;
+    }
+
+    iobuf->data = newdata;
+    iobuf->size = newsize;
+    return EOK;
+}
+
+static inline uint8_t *iobuf_ptr(struct sss_iobuf *iobuf)
+{
+    return iobuf->data + iobuf->dp;
+}
+
+errno_t sss_iobuf_read(struct sss_iobuf *iobuf,
+                       size_t len,
+                       uint8_t *_buf,
+                       size_t *_read)
+{
+    size_t remaining;
+
+    if (iobuf == NULL || _buf == NULL) {
+        return EINVAL;
+    }
+
+    remaining = sss_iobuf_get_len(iobuf);
+    if (len > remaining) {
+        len = remaining;
+    }
+
+    safealign_memcpy(_buf, iobuf_ptr(iobuf), len, &iobuf->dp);
+    if (_read != NULL) {
+        *_read = len;
+    }
+
+    return EOK;
+}
+
+void sss_iobuf_rewind(struct sss_iobuf *iobuf)
+{
+    if (iobuf == NULL) {
+        return;
+    }
+    iobuf->dp = 0;
+}
+
+errno_t sss_iobuf_write_len(struct sss_iobuf *iobuf,
+                            uint8_t *buf,
+                            size_t len)
+{
+    errno_t ret;
+
+    if (iobuf == NULL || buf == NULL) {
+        return EINVAL;
+    }
+
+    ret = ensure_bytes(iobuf, len);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    safealign_memcpy(iobuf_ptr(iobuf), buf, len, &iobuf->dp);
+    return EOK;
+}
diff --git a/src/util/sss_iobuf.h b/src/util/sss_iobuf.h
new file mode 100644
index 0000000..84f8d21
--- /dev/null
+++ b/src/util/sss_iobuf.h
@@ -0,0 +1,45 @@
+#ifndef __SSS_IOBUF_H_
+#define __SSS_IOBUF_H_
+
+#include <talloc.h>
+#include <stdint.h>
+#include <errno.h>
+
+#include "util/util.h"
+#include "util/sss_iobuf.h"
+
+struct sss_iobuf {
+    uint8_t *data;          /* start of the data buffer */
+
+    size_t dp;              /* data pointer */
+    size_t size;            /* current data buffer size */
+    size_t capacity;        /* maximum capacity */
+};
+
+/*
+ * Allocate an IO buffer with an initial size. When written
+ * into, can extend up to max_size
+ */
+struct sss_iobuf *sss_iobuf_tc(TALLOC_CTX *mem_ctx,
+                               size_t size,
+                               size_t capacity);
+
+/*
+ * Init from an existing buffer. Useful for parsing input.
+ */
+errno_t sss_iobuf_init(struct sss_iobuf *iobuf,
+                       uint8_t *data,
+                       size_t size);
+
+errno_t sss_iobuf_read(struct sss_iobuf *iobuf,
+                       size_t len,
+                       uint8_t *_buf,
+                       size_t *_read);
+
+void sss_iobuf_rewind(struct sss_iobuf *iobuf);
+
+errno_t sss_iobuf_write_len(struct sss_iobuf *iobuf,
+                            uint8_t *buf,
+                            size_t len);
+
+#endif /* __SSS_IOBUF_H_ */

From 25780b06645656a6b6d5f85313ae49aba425bdea Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhro...@redhat.com>
Date: Thu, 12 Jan 2017 13:00:21 +0100
Subject: [PATCH 3/5] BUILD: Detect libcurl during configure

Currently libcurl is optional and if not present, just silently skipped.
---
 configure.ac            |  1 +
 src/external/libcurl.m4 | 23 +++++++++++++++++++++++
 2 files changed, 24 insertions(+)
 create mode 100644 src/external/libcurl.m4

diff --git a/configure.ac b/configure.ac
index d264abf..e6a3b4e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -193,6 +193,7 @@ m4_include([src/external/libresolv.m4])
 m4_include([src/external/intgcheck.m4])
 m4_include([src/external/systemtap.m4])
 m4_include([src/external/service.m4])
+m4_include([src/external/libcurl.m4])
 
 if test x$with_secrets = xyes; then
     m4_include([src/external/libhttp_parser.m4])
diff --git a/src/external/libcurl.m4 b/src/external/libcurl.m4
new file mode 100644
index 0000000..c916f03
--- /dev/null
+++ b/src/external/libcurl.m4
@@ -0,0 +1,23 @@
+AC_ARG_ENABLE([curl],
+              [AS_HELP_STRING([--disable-curl-support],
+                              [do not build with libcurl support])],
+              [enable_libcurl=$enableval],
+              [enable_libcurl=yes])
+
+found_libcurl="no"
+AS_IF([test x$enable_libcurl = xyes],
+      [PKG_CHECK_MODULES([CURL],
+                         [libcurl],
+                         [found_libcurl=yes],
+                         [AC_MSG_WARN([
+The libcurl development library was not found. Some features will be disabled.])
+      ])])
+
+AC_SUBST(CURL_LIBS)
+AC_SUBST(CURL_CFLAGS)
+
+AM_CONDITIONAL([BUILD_WITH_LIBCURL],
+               [test x$found_libcurl = xyes])
+
+AM_COND_IF([BUILD_WITH_LIBCURL],
+           [AC_DEFINE_UNQUOTED(HAVE_LIBCURL, 1, [Build with libcurl support])])

From 1ca95031cec453997fd9faca45c4bfc7d0745d9c Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhro...@redhat.com>
Date: Thu, 12 Jan 2017 13:00:48 +0100
Subject: [PATCH 4/5] UTIL: Add a libtevent libcurl wrapper

Adds a request that enables the caller to issue an asynchronous request
with libcurl. Currently only requests towards UNIX sockets are
supported.
---
 Makefile.am         |   1 +
 src/util/tev_curl.c | 818 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/util/tev_curl.h |  71 +++++
 3 files changed, 890 insertions(+)
 create mode 100644 src/util/tev_curl.c
 create mode 100644 src/util/tev_curl.h

diff --git a/Makefile.am b/Makefile.am
index 835f641..789bc7d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -636,6 +636,7 @@ dist_noinst_HEADERS = \
     src/util/util_sss_idmap.h \
     src/util/util_creds.h \
     src/util/sss_iobuf.h \
+    src/util/tev_curl.h \
     src/monitor/monitor.h \
     src/monitor/monitor_interfaces.h \
     src/monitor/monitor_iface_generated.h \
diff --git a/src/util/tev_curl.c b/src/util/tev_curl.c
new file mode 100644
index 0000000..6f48feb
--- /dev/null
+++ b/src/util/tev_curl.c
@@ -0,0 +1,818 @@
+/*
+   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 IOBUF_CHUNK   1024
+#define IOBUF_MAX     4096
+
+static bool global_is_curl_initialized;
+
+struct tcurl_ctx {
+    struct tevent_context *ev;
+    struct tevent_timer *initial_timer;
+
+    CURLM *multi_handle;
+    struct curl_slist *curl_headers;
+};
+
+struct tcurl_sock {
+    struct tcurl_ctx *tctx;
+
+    curl_socket_t sockfd;
+    struct tevent_fd *fde;
+};
+
+struct tcurl_http_state {
+    struct tcurl_ctx *tctx;
+    const char *socket_path;
+    const char *url;
+    int timeout;
+
+    CURL *http_handle;
+
+    struct sss_iobuf *inbuf;
+    struct sss_iobuf *outbuf;
+    int http_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
+     */
+    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;
+    default:
+        break;
+    }
+
+    return EIO;
+}
+
+static const char *http_req2str(enum tcurl_http_request req)
+{
+    switch (req) {
+    case HTTP_GET:
+        return "GET";
+    case HTTP_PUT:
+        return "PUT";
+    case HTTP_DELETE:
+        return "DELETE";
+    }
+
+    return "Uknown request type";
+}
+
+static errno_t tcurl_global_init(void)
+{
+    int 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;
+    char *done_url;
+    errno_t ret;
+    struct tcurl_http_state *state;
+
+    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 */
+        } else {
+            DEBUG(SSSDBG_TRACE_FUNC, "Handled %s\n", done_url);
+        }
+    }
+
+    crv = curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, &req);
+    if (crv != CURLE_OK) {
+        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(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;
+    }
+
+    crv = curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &state->http_code);
+    if (crv != CURLE_OK) {
+        DEBUG(SSSDBG_OP_FAILURE, "Cannot get HTTP status code\n");
+        tevent_req_error(req, EFAULT);
+        return;
+    }
+
+    tevent_req_done(req);
+}
+
+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;
+
+    curl_multi_socket_action(tcs->tctx->multi_handle,
+                             tcs->sockfd,
+                             curl_flags,
+                             &running_handles);
+
+    process_curl_activity(tctx);
+}
+
+
+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;
+}
+
+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:
+        flags = curl2tev_flags(action);
+
+        if (socketp == NULL) {
+            tcsock = register_curl_socket(tctx, s, flags);
+            if (tcsock == NULL) {
+                return 1;
+            }
+        } else {
+            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:
+        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);
+        }
+        curl_multi_assign(tctx->multi_handle, s, NULL);
+        talloc_free(tcsock);
+        break;
+
+    default:
+        return 1;
+  }
+
+  return 0;
+}
+
+static errno_t add_headers(struct tcurl_ctx *tctx,
+                           const char *headers[])
+{
+    if (headers == NULL) {
+        return EOK;
+    }
+
+    for (int i = 0; headers[i] != NULL; i++) {
+        tctx->curl_headers = curl_slist_append(tctx->curl_headers, headers[i]);
+        if (tctx->curl_headers == NULL) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "Cannot add header %s\n", headers[i]);
+            return ENOMEM;
+        }
+    }
+
+    return EOK;
+}
+
+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);
+    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);
+
+    process_curl_activity(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);
+
+    if (timeout_ms == -1) {
+        /* man curlmopt_timerfunction(3) says:
+         *  A timeout_ms value of -1 means you should delete your timer.
+         */
+        talloc_zfree(tctx->initial_timer);
+    }
+
+    tv = tevent_timeval_current_ofs(0, timeout_ms * 10);
+
+    tctx->initial_timer = tevent_add_timer(tctx->ev, tctx, tv,
+                                           check_fd_activity, tctx);
+    if (tctx->initial_timer == NULL) {
+        return -1;
+    }
+
+    return 0;
+}
+
+static int tcurl_ctx_destroy(TALLOC_CTX *ptr)
+{
+    struct tcurl_ctx *ctx = talloc_get_type(ptr, struct tcurl_ctx);
+
+    if (ctx == NULL) {
+        return 0;
+    }
+
+    curl_multi_cleanup(ctx->multi_handle);
+    curl_slist_free_all(ctx->curl_headers);
+    return 0;
+}
+
+struct tcurl_ctx *tcurl_init(TALLOC_CTX *mem_ctx,
+                             struct tevent_context *ev,
+                             const char *headers[])
+{
+    errno_t ret;
+    struct tcurl_ctx *tctx = NULL;
+    CURLMcode cmret;
+
+    ret = tcurl_global_init();
+    if (ret != EOK) {
+        return NULL;
+    }
+
+    tctx = talloc_zero(mem_ctx, struct tcurl_ctx);
+    if (tctx == NULL) {
+        return NULL;
+    }
+    tctx->ev = ev;
+
+    ret = add_headers(tctx, headers);
+    if (ret != EOK) {
+        talloc_free(tctx);
+        return NULL;
+    }
+
+    tctx->multi_handle = curl_multi_init();
+    if (tctx->multi_handle == NULL) {
+        return NULL;
+    }
+    talloc_set_destructor((TALLOC_CTX *) 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));
+        talloc_free(tctx);
+        return NULL;
+    }
+
+    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));
+        talloc_free(tctx);
+        return NULL;
+    }
+
+    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));
+        talloc_free(tctx);
+        return NULL;
+    }
+
+    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));
+        talloc_free(tctx);
+        return NULL;
+    }
+
+    return tctx;
+}
+
+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,
+                                   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_tc(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);
+
+    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_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_easy_cleanup(state->http_handle);
+    return 0;
+}
+
+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->tctx->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 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;
+        }
+
+        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 HTTP_GET:
+        /* GET just needs the write callbacks, nothing to do here.. */
+        break;
+    case 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)
+{
+    errno_t ret;
+    size_t realsize = size * nmemb;
+    struct sss_iobuf *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));
+        return 0;
+    }
+
+    return realsize;
+}
+
+static size_t tcurl_http_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;
+
+    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;
+}
+
+int tcurl_http_recv(TALLOC_CTX *mem_ctx,
+                    struct tevent_req *req,
+                    int *_http_code,
+                    struct sss_iobuf **_outbuf)
+{
+    struct tcurl_http_state *state = tevent_req_data(req, struct tcurl_http_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    if (_http_code != NULL) {
+        *_http_code = state->http_code;
+    }
+
+    if (_outbuf != NULL) {
+        *_outbuf = talloc_steal(mem_ctx, state->outbuf);
+    }
+
+    return 0;
+}
diff --git a/src/util/tev_curl.h b/src/util/tev_curl.h
new file mode 100644
index 0000000..df607e6
--- /dev/null
+++ b/src/util/tev_curl.h
@@ -0,0 +1,71 @@
+/*
+   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/>.
+*/
+
+#ifndef __TEV_CURL_H
+#define __TEV_CURL_H
+
+#include <talloc.h>
+#include <tevent.h>
+
+#include "util/sss_iobuf.h"
+
+/* Supported HTTP requests */
+enum tcurl_http_request {
+    HTTP_GET,
+    HTTP_PUT,
+    HTTP_DELETE,
+};
+
+/*
+ * Initialize the tcurl tevent wrapper. Headers are a NULL-terminated
+ * array of strings such as:
+ *   static const char *headers[] = {
+ *       "Content-type: application/octet-stream",
+ *       NULL,
+ *   };
+ */
+struct tcurl_ctx *tcurl_init(TALLOC_CTX *mem_ctx,
+                             struct tevent_context *ev,
+                             const char *headers[]);
+
+/*
+ * Run a single request. Currently only UNIX sockets at socket_path are supported.
+ * The timeout parameter defaults to 0 if not specified.
+ *
+ * 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.
+ */
+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,
+                                   struct sss_iobuf *req_data,
+                                   int timeout);
+
+int tcurl_http_recv(TALLOC_CTX *mem_ctx,
+                    struct tevent_req *req,
+                    int *_http_code,
+                    struct sss_iobuf **_outbuf);
+
+#endif /* __TEV_CURL_H */

From a2e07a196fc6fb4668a08b62edcd57ea6950da24 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhro...@redhat.com>
Date: Fri, 13 Jan 2017 09:31:03 +0100
Subject: [PATCH 5/5] TESTS: test the curl wrapper with a command-line tool

In order to test the curl integration code, this patch adds a
command-line tool and tests that it's possible to drive a conversation
with the secrets responder using the tool.
---
 Makefile.am                    |  21 +++++
 contrib/ci/deps.sh             |   2 +
 src/tests/intg/Makefile.am     |   1 +
 src/tests/intg/config.py.m4    |   1 +
 src/tests/intg/test_secrets.py | 142 +++++++++++++++++++++++++++-
 src/tests/tcurl_test_tool.c    | 207 +++++++++++++++++++++++++++++++++++++++++
 6 files changed, 371 insertions(+), 3 deletions(-)
 create mode 100644 src/tests/tcurl_test_tool.c

diff --git a/Makefile.am b/Makefile.am
index 789bc7d..5b5b980 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -307,6 +307,9 @@ check_PROGRAMS = \
 if HAVE_CMOCKA
 check_PROGRAMS += dummy-child
 endif # HAVE_CMOCKA
+if BUILD_WITH_LIBCURL
+check_PROGRAMS += tcurl-test-tool
+endif # BUILD_WITH_LIBCURL
 
 PYTHON_TESTS =
 
@@ -2088,6 +2091,23 @@ krb5_child_test_LDADD = \
     $(SSSD_INTERNAL_LTLIBS) \
     libsss_test_common.la
 
+if BUILD_WITH_LIBCURL
+tcurl_test_tool_SOURCES = \
+    src/tests/tcurl_test_tool.c \
+    src/util/tev_curl.c \
+    src/util/sss_iobuf.c \
+    $(NULL)
+tcurl_test_tool_CFLAGS = \
+    $(AM_CFLAGS) \
+    $(CURL_CFLAGS) \
+    $(NULL)
+tcurl_test_tool_LDADD = \
+    $(CURL_LIBS) \
+    $(SSSD_LIBS) \
+    $(SSSD_INTERNAL_LTLIBS) \
+    $(NULL)
+endif
+
 if BUILD_DBUS_TESTS
 
 sbus_tests_SOURCES = \
@@ -3214,6 +3234,7 @@ intgcheck-prepare:
 	    --without-semanage \
 	    $(INTGCHECK_CONFIGURE_FLAGS); \
 	$(MAKE) $(AM_MAKEFLAGS); \
+	$(MAKE) $(AM_MAKEFLAGS) tcurl-test-tool; \
 	: Force single-thread install to workaround concurrency issues; \
 	$(MAKE) $(AM_MAKEFLAGS) -j1 install; \
 	: Remove .la files from LDB module directory to avoid loader warnings; \
diff --git a/contrib/ci/deps.sh b/contrib/ci/deps.sh
index 387ad1f..c525e62 100644
--- a/contrib/ci/deps.sh
+++ b/contrib/ci/deps.sh
@@ -46,6 +46,7 @@ if [[ "$DISTRO_BRANCH" == -redhat-* ]]; then
         rpm-build
         uid_wrapper
         python-requests
+        curl-devel
     )
     _DEPS_LIST_SPEC=`
         sed -e 's/@PACKAGE_VERSION@/0/g' \
@@ -120,6 +121,7 @@ if [[ "$DISTRO_BRANCH" == -debian-* ]]; then
         systemtap-sdt-dev
         libhttp-parser-dev
         libjansson-dev
+        libcurl4-openssl-dev
     )
     DEPS_INTGCHECK_SATISFIED=true
 fi
diff --git a/src/tests/intg/Makefile.am b/src/tests/intg/Makefile.am
index e81e5ee..29723fd 100644
--- a/src/tests/intg/Makefile.am
+++ b/src/tests/intg/Makefile.am
@@ -31,6 +31,7 @@ config.py: config.py.m4
 	   -D "secdbpath=\`$(secdbpath)'" \
 	   -D "libexecpath=\`$(libexecdir)'" \
 	   -D "runstatedir=\`$(runstatedir)'" \
+	   -D "abs_builddir=\`$(abs_builddir)'" \
 	   $< > $@
 
 root:
diff --git a/src/tests/intg/config.py.m4 b/src/tests/intg/config.py.m4
index 65e17e5..841aae0 100644
--- a/src/tests/intg/config.py.m4
+++ b/src/tests/intg/config.py.m4
@@ -15,3 +15,4 @@ MCACHE_PATH         = "mcpath"
 SECDB_PATH          = "secdbpath"
 LIBEXEC_PATH        = "libexecpath"
 RUNSTATEDIR         = "runstatedir"
+ABS_BUILDDIR        = "abs_builddir"
diff --git a/src/tests/intg/test_secrets.py b/src/tests/intg/test_secrets.py
index 062dcb6..2f98dc9 100644
--- a/src/tests/intg/test_secrets.py
+++ b/src/tests/intg/test_secrets.py
@@ -69,6 +69,7 @@ def sec_teardown():
         for secdb_file in os.listdir(config.SECDB_PATH):
             os.unlink(config.SECDB_PATH + "/" + secdb_file)
     request.addfinalizer(sec_teardown)
+    return secpid
 
 
 @pytest.fixture
@@ -91,17 +92,28 @@ def setup_for_secrets(request):
     """).format(**locals())
 
     create_conf_fixture(request, conf)
-    create_sssd_secrets_fixture(request)
-    return None
+    secpid = create_sssd_secrets_fixture(request)
+    return secpid
 
+def get_secrets_socket():
+    return os.path.join(config.RUNSTATEDIR, "secrets.socket")
 
 @pytest.fixture
 def secrets_cli(request):
-    sock_path = os.path.join(config.RUNSTATEDIR, "secrets.socket")
+    sock_path = get_secrets_socket()
     cli = SecretsLocalClient(sock_path=sock_path)
     return cli
 
 
+@pytest.fixture
+def curlwrap_tool(request):
+    curlwrap_path = os.path.join(config.ABS_BUILDDIR, "..", "..", "..", "tcurl-test-tool")
+    if os.access(curlwrap_path, os.X_OK):
+        return curlwrap_path
+
+    return None
+
+
 def test_crd_ops(setup_for_secrets, secrets_cli):
     """
     Test that the basic Create, Retrieve, Delete operations work
@@ -171,6 +183,130 @@ def test_crd_ops(setup_for_secrets, secrets_cli):
     assert str(err413.value).startswith("413")
 
 
+def run_curlwrap_tool(args, exp_http_code):
+    cmd = subprocess.Popen(args,
+                           stdin=subprocess.PIPE,
+                           stdout=subprocess.PIPE,
+                           stderr=subprocess.PIPE)
+    out, _ = cmd.communicate()
+
+    assert cmd.returncode == 0
+
+    exp_http_code_str = "Request HTTP code: %d" % exp_http_code
+    assert exp_http_code_str in out
+
+    return out
+
+
+def test_curlwrap_crd_ops(setup_for_secrets,
+                          curlwrap_tool):
+    """
+    Test that the basic Create, Retrieve, Delete operations work using our
+    tevent libcurl code
+    """
+    if not curlwrap_tool:
+        pytest.skip("The tcurl tool is not available, skipping test")
+    sock_path = get_secrets_socket()
+
+    # listing an empty DB yields a 404
+    run_curlwrap_tool([curlwrap_tool,
+                      '-v', '-s', sock_path,
+                      'http://localhost/secrets/'],
+                      404)
+
+    # listing a non-existent secret yields a 404
+    run_curlwrap_tool([curlwrap_tool,
+                      '-v', '-s', sock_path,
+                      'http://localhost/secrets/foo'],
+                      404)
+
+    # set a secret foo:bar
+    run_curlwrap_tool([curlwrap_tool, '-p',
+                      '-v', '-s', sock_path,
+                      'http://localhost/secrets/foo',
+                      'bar'],
+                      200)
+
+    # list secrets
+    output = run_curlwrap_tool([curlwrap_tool,
+                                '-v', '-s', sock_path,
+                                'http://localhost/secrets/'],
+                                200)
+    assert "foo" in output
+
+    # get the foo secret
+    output = run_curlwrap_tool([curlwrap_tool,
+                                '-v', '-s', sock_path,
+                                'http://localhost/secrets/foo'],
+                                200)
+    assert "bar" in output
+
+    # Overwriting a secret is an error
+    run_curlwrap_tool([curlwrap_tool, '-p',
+                      '-v', '-s', sock_path,
+                      'http://localhost/secrets/foo',
+                      'baz'],
+                      409)
+
+    # Delete a secret
+    run_curlwrap_tool([curlwrap_tool, '-d',
+                      '-v', '-s', sock_path,
+                      'http://localhost/secrets/foo'],
+                      200)
+
+    # Delete a non-existent secret must yield a 404
+    run_curlwrap_tool([curlwrap_tool, '-d',
+                      '-v', '-s', sock_path,
+                      'http://localhost/secrets/foo'],
+                      404)
+
+def test_curlwrap_parallel(setup_for_secrets,
+                           curlwrap_tool):
+    """
+    The tevent libcurl wrapper is meant to be non-blocking. Test
+    its operation in parallel.
+    """
+    if not curlwrap_tool:
+        pytest.skip("The tcurl tool is not available, skipping test")
+    sock_path = get_secrets_socket()
+
+    secrets = dict()
+    nsecrets = 10
+    action = '-g'
+
+    for i in xrange(0, nsecrets):
+        secrets["key" + str(i)] = "value" + str(i)
+
+    args = [curlwrap_tool, '-p', '-v', '-s', sock_path]
+    for skey, svalue in secrets.iteritems():
+        args.extend(['http://localhost/secrets/%s' % skey, svalue])
+    run_curlwrap_tool(args, 200)
+
+    output = run_curlwrap_tool([curlwrap_tool,
+                                '-v', '-s', sock_path,
+                                'http://localhost/secrets/'],
+                                200)
+    for skey in secrets.iterkeys():
+        assert skey in output
+
+    args = [curlwrap_tool, '-g', '-v', '-s', sock_path]
+    for skey in secrets.iterkeys():
+        args.extend(['http://localhost/secrets/%s' % skey])
+    output = run_curlwrap_tool(args, 200)
+
+    for svalue in secrets.itervalues():
+        assert svalue in output
+
+    args = [curlwrap_tool, '-d', '-v', '-s', sock_path]
+    for skey in secrets.iterkeys():
+        args.extend(['http://localhost/secrets/%s' % skey])
+    output = run_curlwrap_tool(args, 200)
+
+    run_curlwrap_tool([curlwrap_tool,
+                      '-v', '-s', sock_path,
+                      'http://localhost/secrets/'],
+                      404)
+
 def test_containers(setup_for_secrets, secrets_cli):
     """
     Test that storing secrets inside containers works
diff --git a/src/tests/tcurl_test_tool.c b/src/tests/tcurl_test_tool.c
new file mode 100644
index 0000000..17d8e8a
--- /dev/null
+++ b/src/tests/tcurl_test_tool.c
@@ -0,0 +1,207 @@
+/*
+   SSSD
+
+   libcurl tevent integration test tool
+
+   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 <popt.h>
+
+#include "util/util.h"
+#include "util/tev_curl.h"
+
+#define MAXREQ  64
+
+struct tool_ctx {
+    bool verbose;
+
+    errno_t error;
+    bool done;
+
+    size_t nreqs;
+};
+
+static void request_done(struct tevent_req *req)
+{
+    int http_code;
+    struct sss_iobuf *outbuf;
+    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);
+    talloc_zfree(req);
+
+    if (tool_ctx->error != EOK) {
+        DEBUG(SSSDBG_FATAL_FAILURE, "HTTP request failed: %d\n", tool_ctx->error);
+        return;
+    } else if (tool_ctx->verbose) {
+        printf("Request HTTP code: %d\n", http_code);
+        printf("Request HTTP body: \n%s\n", (const char *) outbuf->data);
+        talloc_zfree(outbuf->data);
+    }
+
+    tool_ctx->nreqs--;
+    if (tool_ctx->nreqs == 0) {
+        tool_ctx->done = true;
+    }
+}
+
+int main(int argc, const char *argv[])
+{
+    int opt;
+    poptContext pc;
+
+    int pc_debug = 0;
+    int pc_verbose = 0;
+    const char *socket_path = NULL;
+    const char *extra_arg_ptr;
+
+    static const char *headers[] = {
+        "Content-type: application/octet-stream",
+        NULL,
+    };
+
+    struct poptOption long_options[] = {
+        POPT_AUTOHELP
+        { "debug", '\0', POPT_ARG_INT, &pc_debug, 0,
+          "The debug level to run with", NULL },
+        { "socket-path", 's', POPT_ARG_STRING, &socket_path, 0,
+          "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 },
+        { "verbose", 'v', POPT_ARG_NONE, NULL, 'v', "Print response code and body", NULL },
+        POPT_TABLEEND
+    };
+
+    errno_t ret;
+    struct tevent_req *req;
+    struct tevent_context *ev;
+    enum tcurl_http_request req_type = HTTP_GET;
+    struct tcurl_ctx *ctx;
+    struct tool_ctx *tool_ctx;
+
+    const char *urls[MAXREQ] = { 0 };
+    struct sss_iobuf inbufs[MAXREQ];
+
+    size_t n_reqs = 0;
+
+    debug_prg_name = argv[0];
+    pc = poptGetContext(NULL, argc, argv, long_options, 0);
+    poptSetOtherOptionHelp(pc, "HTTPDATA");
+
+    while ((opt = poptGetNextOpt(pc)) > 0) {
+        switch(opt) {
+        case 'g':
+            req_type = HTTP_GET;
+            break;
+        case 'p':
+            req_type = HTTP_PUT;
+            break;
+        case 'd':
+            req_type = HTTP_DELETE;
+            break;
+        case 'v':
+            pc_verbose = 1;
+            break;
+        default:
+            DEBUG(SSSDBG_FATAL_FAILURE, "Unexpected option\n");
+            return 1;
+        }
+    }
+
+    DEBUG_CLI_INIT(pc_debug);
+
+    while ((extra_arg_ptr = poptGetArg(pc)) != NULL) {
+        switch(req_type) {
+        case HTTP_GET:
+        case HTTP_DELETE:
+            urls[n_reqs++] = extra_arg_ptr;
+            break;
+        case HTTP_PUT:
+            if (urls[n_reqs] == NULL) {
+                urls[n_reqs] = extra_arg_ptr;
+            } else {
+                ret = sss_iobuf_init(&inbufs[n_reqs],
+                                     (uint8_t *) discard_const(extra_arg_ptr),
+                                     strlen(extra_arg_ptr));
+                if (ret != EOK) {
+                    DEBUG(SSSDBG_CRIT_FAILURE, "Could not init input buffer\n");
+                    return 1;
+                }
+                n_reqs++;
+            }
+            break;
+        }
+    }
+
+    if (opt != -1) {
+        poptPrintUsage(pc, stderr, 0);
+        fprintf(stderr, "%s", poptStrerror(opt));
+        return 1;
+    }
+
+    if (!socket_path) {
+        DEBUG(SSSDBG_FATAL_FAILURE, "Please specify the socket path\n");
+        poptPrintUsage(pc, stderr, 0);
+        return 1;
+    }
+
+    tool_ctx = talloc_zero(NULL, struct tool_ctx);
+    if (tool_ctx == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Could not init tool context\n");
+        return 1;
+    }
+    tool_ctx->nreqs = n_reqs;
+    tool_ctx->verbose = !!pc_verbose;
+
+    ev = tevent_context_init(tool_ctx);
+    if (ev == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Could not init tevent context\n");
+        return 1;
+    }
+
+    ctx = tcurl_init(tool_ctx, ev, headers);
+    if (ctx == NULL) {
+        DEBUG(SSSDBG_FATAL_FAILURE, "Could not init tcurl context\n");
+        return 1;
+    }
+
+    for (size_t i = 0; i < n_reqs; i++) {
+        req = tcurl_http_send(tool_ctx, ev, ctx,
+                              req_type,
+                              socket_path,
+                              urls[i],
+                              &inbufs[i],
+                              5);
+        if (ctx == NULL) {
+            DEBUG(SSSDBG_FATAL_FAILURE, "Could not create request\n");
+            return 1;
+        }
+        tevent_req_set_callback(req, request_done, tool_ctx);
+    }
+
+    while (tool_ctx->done == false) {
+         tevent_loop_once(ev);
+    }
+
+    talloc_free(tool_ctx);
+    poptFreeContext(pc);
+    return 0;
+}
_______________________________________________
sssd-devel mailing list -- sssd-devel@lists.fedorahosted.org
To unsubscribe send an email to sssd-devel-le...@lists.fedorahosted.org

Reply via email to