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

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 5c51222ba533972d56f4ecb43e05b5c2e1db372e 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 30b5f3c8252a819bdd3ee2094562a019e2252bd8 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 | 194 +++++++++++++++++++++++++++++++++++++++
 src/util/sss_iobuf.c          | 204 ++++++++++++++++++++++++++++++++++++++++++
 src/util/sss_iobuf.h          | 117 ++++++++++++++++++++++++
 4 files changed, 537 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 e676e18..b612897 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
@@ -654,6 +655,7 @@ dist_noinst_HEADERS = \
     src/util/util_sss_idmap.h \
     src/util/util_creds.h \
     src/util/inotify.h \
+    src/util/sss_iobuf.h \
     src/monitor/monitor.h \
     src/monitor/monitor_interfaces.h \
     src/monitor/monitor_iface_generated.h \
@@ -1820,6 +1822,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 \
@@ -2095,6 +2098,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 \
@@ -2765,6 +2769,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) \
@@ -2783,6 +2788,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) \
@@ -3145,6 +3151,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 = \
@@ -3476,6 +3495,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 = \
@@ -3705,6 +3725,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 \
@@ -3738,6 +3759,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..eaee966
--- /dev/null
+++ b/src/tests/cmocka/test_iobuf.c
@@ -0,0 +1,194 @@
+/*
+    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 *rb;
+
+    rb = sss_iobuf_init_readonly(NULL, buffer, sizeof(buffer));
+    assert_non_null(rb);
+
+    ret = sss_iobuf_read(rb, 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(rb, 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(rb, 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(rb, 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(rb, 10, NULL, &nread);
+    assert_int_equal(ret, EINVAL);
+
+    talloc_free(rb);
+}
+
+static void test_sss_iobuf_write(void **state)
+{
+    struct sss_iobuf *wb;
+    struct sss_iobuf *rb;
+    size_t hwlen = sizeof("Hello world");   /* Includes trailing zero */
+    uint8_t readbuf[64];
+    size_t nread;
+    errno_t ret;
+
+    /* Exactly fill the capacity */
+    wb = sss_iobuf_init_empty(NULL, hwlen, hwlen);
+    assert_non_null(wb);
+    ret = sss_iobuf_write_len(wb,
+                              (uint8_t *) discard_const("Hello world"),
+                              sizeof("Hello world"));
+    assert_int_equal(ret, EOK);
+
+    rb = sss_iobuf_init_readonly(NULL,
+                                 sss_iobuf_get_data(wb),
+                                 sss_iobuf_get_len(wb));
+    talloc_free(wb);
+    assert_non_null(rb);
+
+    ret = sss_iobuf_read(rb, 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(rb);
+
+    /* Overflow the capacity by one */
+    wb = sss_iobuf_init_empty(NULL, hwlen, hwlen);
+    assert_non_null(wb);
+    ret = sss_iobuf_write_len(wb,
+                              (uint8_t *) discard_const("Hello world!"),
+                              sizeof("Hello world!"));
+    assert_int_not_equal(ret, EOK);
+    talloc_zfree(wb);
+
+    /* Test resizing exactly up to capacity in several writes */
+    wb = sss_iobuf_init_empty(NULL, 2, hwlen);
+    assert_non_null(wb);
+
+    ret = sss_iobuf_write_len(wb,
+                              (uint8_t *) discard_const("Hello "),
+                              sizeof("Hello ")-1); /* Not the null byte now.. */
+    assert_int_equal(ret, EOK);
+    ret = sss_iobuf_write_len(wb,
+                              (uint8_t *) discard_const("world"),
+                              sizeof("world"));
+    assert_int_equal(ret, EOK);
+
+    rb = sss_iobuf_init_readonly(NULL,
+                                 sss_iobuf_get_data(wb),
+                                 sss_iobuf_get_len(wb));
+    talloc_free(wb);
+    assert_non_null(rb);
+
+    ret = sss_iobuf_read(rb, 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(rb);
+
+    /* Overflow the capacity during a resize by one */
+    wb = sss_iobuf_init_empty(NULL, 2, hwlen);
+    assert_non_null(wb);
+
+    ret = sss_iobuf_write_len(wb,
+                              (uint8_t *) discard_const("Hello "),
+                              sizeof("Hello ")-1); /* Not the null byte now.. */
+    assert_int_equal(ret, EOK);
+    ret = sss_iobuf_write_len(wb,
+                              (uint8_t *) discard_const("world!"),
+                              sizeof("world!"));
+    assert_int_not_equal(ret, EOK);
+    talloc_zfree(wb);
+
+    /* Test allocating an unlimited buffer */
+    wb = sss_iobuf_init_empty(NULL, 2, 0);
+    assert_non_null(wb);
+
+    ret = sss_iobuf_write_len(wb,
+                              (uint8_t *) discard_const("Hello "),
+                              sizeof("Hello ")-1); /* Not the null byte now.. */
+    assert_int_equal(ret, EOK);
+    ret = sss_iobuf_write_len(wb,
+                              (uint8_t *) discard_const("world"),
+                              sizeof("world"));
+    assert_int_equal(ret, EOK);
+
+    rb = sss_iobuf_init_readonly(NULL,
+                                 sss_iobuf_get_data(wb),
+                                 sss_iobuf_get_len(wb));
+    talloc_free(wb);
+    assert_non_null(rb);
+
+    ret = sss_iobuf_read(rb, 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(rb);
+}
+
+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..a6223b2
--- /dev/null
+++ b/src/util/sss_iobuf.c
@@ -0,0 +1,204 @@
+#include <talloc.h>
+
+#include "util/util.h"
+#include "util/sss_iobuf.h"
+
+/**
+ * @brief The iobuf structure that holds the data, its capacity and
+ * a pointer to the data.
+ *
+ * @see sss_iobuf_init_empty()
+ * @see sss_iobuf_init_readonly()
+ */
+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 */
+};
+
+struct sss_iobuf *sss_iobuf_init_empty(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;
+    }
+
+    if (capacity == 0) {
+        capacity = SIZE_MAX/2;
+    }
+
+    iobuf->data = buf;
+    iobuf->size = size;
+    iobuf->capacity = capacity;
+    iobuf->dp = 0;
+
+    return iobuf;
+}
+
+struct sss_iobuf *sss_iobuf_init_readonly(TALLOC_CTX *mem_ctx,
+                                          uint8_t *data,
+                                          size_t size)
+{
+    struct sss_iobuf *iobuf;
+
+    iobuf = sss_iobuf_init_empty(mem_ctx, size, size);
+    if (iobuf == NULL) {
+        return NULL;
+    }
+
+    if (data != NULL) {
+        memcpy(iobuf->data, data, size);
+    }
+
+    return iobuf;
+}
+
+size_t sss_iobuf_get_len(struct sss_iobuf *iobuf)
+{
+    if (iobuf == NULL) {
+        return 0;
+    }
+
+    return iobuf->dp;
+}
+
+size_t sss_iobuf_get_capacity(struct sss_iobuf *iobuf)
+{
+    if (iobuf == NULL) {
+        return 0;
+    }
+
+    return iobuf->capacity;
+}
+
+size_t sss_iobuf_get_size(struct sss_iobuf *iobuf)
+{
+    if (iobuf == NULL) {
+        return 0;
+    }
+
+    return iobuf->size;
+}
+
+uint8_t *sss_iobuf_get_data(struct sss_iobuf *iobuf)
+{
+    if (iobuf == NULL) {
+        return NULL;
+    }
+
+    return iobuf->data;
+}
+
+static size_t 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 = 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;
+}
+
+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..e546d59
--- /dev/null
+++ b/src/util/sss_iobuf.h
@@ -0,0 +1,117 @@
+#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"
+
+/*
+ * @brief Allocate an empty IO buffer
+ *
+ * @param[in]  mem_ctx      The talloc context that owns the iobuf
+ *
+ * When this buffer is written into, but the capacity is exceeded, the write
+ * function will return an error.
+ *
+ * @param[in]  mem_ctx      The talloc context that owns the iobuf
+ * @param[in]  size         The size of the data buffer
+ * @param[in]  capacity     The maximum capacity the buffer can grow into.
+ *                          Use 0 for an 'unlimited' buffer that will grow
+ *                          until SIZE_MAX/2.
+ *
+ * @return The newly created buffer on success or NULL on an error.
+ *
+ */
+struct sss_iobuf *sss_iobuf_init_empty(TALLOC_CTX *mem_ctx,
+                                       size_t size,
+                                       size_t capacity);
+
+/*
+ * @brief Allocate an IO buffer with a fixed size
+ *
+ * This function is useful for parsing an input buffer from an existing
+ * buffer pointed to by data.
+ *
+ * The iobuf does not assume ownership of the data buffer in talloc terms,
+ * but copies the data instead.
+ *
+ * @param[in]  mem_ctx      The talloc context that owns the iobuf
+ * @param[in]  data         The data to initialize the IO buffer with. This
+ *                          data is copied into the iobuf-owned buffer.
+ * @param[in]  size         The size of the data buffer
+ *
+ * @return The newly created buffer on success or NULL on an error.
+ */
+struct sss_iobuf *sss_iobuf_init_readonly(TALLOC_CTX *mem_ctx,
+                                          uint8_t *data,
+                                          size_t size);
+
+/*
+ * @brief Returns the number of bytes currently stored in the iobuf
+ *
+ * @return The number of bytes (the data pointer offset)
+ */
+size_t sss_iobuf_get_len(struct sss_iobuf *iobuf);
+
+/*
+ * @brief Returns the capacity of the IO buffer
+ *
+ * @return The capacity of the IO buffer. Returns zero
+ * for an unlimited buffer.
+ */
+size_t sss_iobuf_get_capacity(struct sss_iobuf *iobuf);
+
+/*
+ * @brief Returns the current size of the IO buffer
+ */
+size_t sss_iobuf_get_size(struct sss_iobuf *iobuf);
+
+/*
+ * @brief Returns the data pointer of the IO buffer
+ */
+uint8_t *sss_iobuf_get_data(struct sss_iobuf *iobuf);
+
+/*
+ * @brief Read from an IO buffer
+ *
+ * Read up to len bytes from an IO buffer. It is not an error to request
+ * more bytes than the buffer actually has - the function will succeed, but
+ * return the actual number of bytes read. Reading from an empty buffer just
+ * returns zero bytes read.
+ *
+ * @param[in]  iobuf        The IO buffer to read from
+ * @param[in]  len          The maximum number of bytes to read
+ * @param[out] _buf         The buffer to read data into from iobuf
+ * @param[out] _read        The actual number of bytes read from IO buffer.
+ *
+ * @return EOK on success, errno otherwise
+ */
+errno_t sss_iobuf_read(struct sss_iobuf *iobuf,
+                       size_t len,
+                       uint8_t *_buf,
+                       size_t *_read);
+
+/*
+ * @brief Write into an IO buffer
+ *
+ * Attempts to write len bytes into the iobuf. If the capacity is exceeded,
+ * the iobuf module tries to extend the buffer up to the maximum capacity.
+ *
+ * If reallocating the internal buffer fails, the data pointers are not
+ * touched.
+ *
+ * @param[in]  iobuf        The IO buffer to write to
+ * @param[in]  buf          The data to write into the buffer
+ * @param[in]  len          The number of bytes to write
+ *
+ * @return EOK on success, errno otherwise. Notably returns ENOBUFS if
+ * the buffer capacity is exceeded.
+ */
+errno_t sss_iobuf_write_len(struct sss_iobuf *iobuf,
+                            uint8_t *buf,
+                            size_t len);
+
+#endif /* __SSS_IOBUF_H_ */

From 1edf42ca346f8108c30fe581d22db3dbb0e26465 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 | 38 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 39 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..3bc303c
--- /dev/null
+++ b/src/external/libcurl.m4
@@ -0,0 +1,38 @@
+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.])
+      ])])
+
+AS_IF([test x"$found_libcurl" = xyes],
+    CFLAGS="$CFLAGS $CURL_CFLAGS"
+
+    AC_MSG_CHECKING([For CURLOPT_UNIX_SOCKET_PATH support in libcurl])
+    AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
+    [[#include <curl/curl.h>
+    CURLoption opt = CURLOPT_UNIX_SOCKET_PATH;
+    ]])],
+                    [have_curlopt_unix_sockpath=yes]
+                    [AC_MSG_RESULT([yes])],
+                    [have_curlopt_unix_sockpath=no]
+                    [AC_MSG_RESULT([no, libcurl support will be disabled])],)
+
+    CFLAGS=$SAVE_CFLAGS
+)
+
+AC_SUBST(CURL_LIBS)
+AC_SUBST(CURL_CFLAGS)
+
+AM_CONDITIONAL([BUILD_WITH_LIBCURL],
+               [test x"$have_curlopt_unix_sockpath" = xyes])
+AM_COND_IF([BUILD_WITH_LIBCURL],
+           [AC_DEFINE_UNQUOTED(HAVE_LIBCURL, 1, [Build with libcurl support])])

From 7ba5383a6c2a31cdaf55359b91cc5d9a6485d3a3 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 | 893 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/util/tev_curl.h | 111 +++++++
 3 files changed, 1005 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 b612897..41b08e4 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -656,6 +656,7 @@ dist_noinst_HEADERS = \
     src/util/util_creds.h \
     src/util/inotify.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..149d5be
--- /dev/null
+++ b/src/util/tev_curl.c
@@ -0,0 +1,893 @@
+/*
+   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;
+
+/**
+ * @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 *initial_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 */
+};
+
+/**
+ * @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;
+    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 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 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";
+    }
+
+    return "Uknown request type";
+}
+
+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 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, &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;
+    }
+
+    /* 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 (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;
+
+    /* 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_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);
+
+    /* https://ec.haxx.se/libcurl-drive-multi-socket.html */
+    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(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;
+}
+
+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;
+        }
+    }
+
+    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_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 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)
+{
+    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));
+        /* zero signifies an EOF */
+        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..de0601d
--- /dev/null
+++ b/src/util/tev_curl.h
@@ -0,0 +1,111 @@
+/*
+   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"
+
+/**
+ * @brief Supported HTTP requests
+ */
+enum tcurl_http_request {
+    TCURL_HTTP_GET,
+    TCURL_HTTP_PUT,
+    TCURL_HTTP_DELETE,
+};
+
+/**
+ * @brief Initialize the tcurl tevent wrapper.
+ *
+ * @returns the opaque context or NULL on error
+ */
+struct tcurl_ctx *tcurl_init(TALLOC_CTX *mem_ctx,
+                             struct tevent_context *ev);
+
+/**
+ * @brief Run a single asynchronous HTTP request.
+ *
+ * Currently only UNIX sockets at socket_path are supported.
+ *
+ * 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:
+ *   static const char *headers[] = {
+ *       "Content-type: application/octet-stream",
+ *       NULL,
+ *   };
+ *
+ * @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]  socket_path  The path to the UNIX socket to forward the
+ *                          request to
+ * @param[in]  url          The request URL
+ * @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
+ *                          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.
+ *
+ * @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_http_recv to read the error code.
+ *
+ * @see tcurl_init
+ * @see tcurl_http_recv
+ */
+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);
+
+/**
+ * @brief Receive a result of a single asynchronous HTTP request.
+ *
+ * @param[in]  mem_ctx      The talloc context that owns the outbuf
+ * @param[in]  req          The request previously obtained with
+ *                          tcurl_http_send
+ * @param[out] _http_code   The HTTP code that the transfer ended with
+ * @param[out] _outbuf      The raw data the HTTP request returned
+ *
+ * @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);
+
+#endif /* __TEV_CURL_H */

From 29295c74dfc2294ed9c46419f0815b0795b5b30a 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 | 143 +++++++++++++++++++++++++-
 src/tests/tcurl_test_tool.c    | 222 +++++++++++++++++++++++++++++++++++++++++
 6 files changed, 389 insertions(+), 1 deletion(-)
 create mode 100644 src/tests/tcurl_test_tool.c

diff --git a/Makefile.am b/Makefile.am
index 41b08e4..d6896f1 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2119,6 +2119,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 = \
@@ -3227,6 +3244,9 @@ endif
 if BUILD_AUTOFS
 noinst_PROGRAMS += autofs_test_client
 endif
+if BUILD_WITH_LIBCURL
+noinst_PROGRAMS += tcurl-test-tool
+endif
 
 pam_test_client_SOURCES = src/sss_client/pam_test_client.c
 pam_test_client_LDADD = $(PAM_LIBS) $(PAM_MISC_LIBS)
@@ -3261,6 +3281,7 @@ intgcheck-prepare:
 	    --without-semanage \
 	    $(INTGCHECK_CONFIGURE_FLAGS); \
 	$(MAKE) $(AM_MAKEFLAGS); \
+	$(MAKE) $(AM_MAKEFLAGS) tcurl-test-tool || echo "libcurl tests will be disabled"; \
 	: 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 64f90b4..1d36fa0 100644
--- a/src/tests/intg/Makefile.am
+++ b/src/tests/intg/Makefile.am
@@ -38,6 +38,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..a8d2332 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
@@ -95,13 +96,27 @@ def setup_for_secrets(request):
     return None
 
 
+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 +186,132 @@ 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..e1e2e1c
--- /dev/null
+++ b/src/tests/tcurl_test_tool.c
@@ -0,0 +1,222 @@
+/*
+   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 *) sss_iobuf_get_data(outbuf));
+        talloc_zfree(outbuf);
+    }
+
+    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
+    };
+
+    struct tevent_req *req;
+    struct tevent_context *ev;
+    enum tcurl_http_request req_type = TCURL_HTTP_GET;
+    struct tcurl_ctx *ctx;
+    struct tool_ctx *tool_ctx;
+
+    const char *urls[MAXREQ] = { 0 };
+    struct sss_iobuf **inbufs;
+
+    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 = TCURL_HTTP_GET;
+            break;
+        case 'p':
+            req_type = TCURL_HTTP_PUT;
+            break;
+        case 'd':
+            req_type = TCURL_HTTP_DELETE;
+            break;
+        case 'v':
+            pc_verbose = 1;
+            break;
+        default:
+            DEBUG(SSSDBG_FATAL_FAILURE, "Unexpected option\n");
+            return 1;
+        }
+    }
+
+    DEBUG_CLI_INIT(pc_debug);
+
+    tool_ctx = talloc_zero(NULL, struct tool_ctx);
+    if (tool_ctx == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Could not init tool context\n");
+        return 1;
+    }
+
+    inbufs = talloc_zero_array(tool_ctx, struct sss_iobuf *, MAXREQ);
+    if (inbufs == NULL) {
+        talloc_zfree(tool_ctx);
+        return 1;
+    }
+
+    while ((extra_arg_ptr = poptGetArg(pc)) != NULL) {
+        switch(req_type) {
+        case TCURL_HTTP_GET:
+        case TCURL_HTTP_DELETE:
+            urls[n_reqs++] = extra_arg_ptr;
+            break;
+        case TCURL_HTTP_PUT:
+            if (urls[n_reqs] == NULL) {
+                urls[n_reqs] = extra_arg_ptr;
+            } else {
+                inbufs[n_reqs] = sss_iobuf_init_readonly(
+                                              inbufs,
+                                              (uint8_t *) discard_const(extra_arg_ptr),
+                                              strlen(extra_arg_ptr));
+                if (inbufs[n_reqs] == NULL) {
+                    DEBUG(SSSDBG_CRIT_FAILURE, "Could not init input buffer\n");
+                    talloc_zfree(tool_ctx);
+                    return 1;
+                }
+                n_reqs++;
+            }
+            break;
+        }
+    }
+
+    if (opt != -1) {
+        poptPrintUsage(pc, stderr, 0);
+        fprintf(stderr, "%s", poptStrerror(opt));
+        talloc_zfree(tool_ctx);
+        return 1;
+    }
+
+    if (!socket_path) {
+        DEBUG(SSSDBG_FATAL_FAILURE, "Please specify the socket path\n");
+        poptPrintUsage(pc, stderr, 0);
+        talloc_zfree(tool_ctx);
+        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");
+        talloc_zfree(tool_ctx);
+        return 1;
+    }
+
+    ctx = tcurl_init(tool_ctx, ev);
+    if (ctx == NULL) {
+        DEBUG(SSSDBG_FATAL_FAILURE, "Could not init tcurl context\n");
+        talloc_zfree(tool_ctx);
+        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],
+                              headers,
+                              inbufs[i],
+                              5);
+        if (ctx == NULL) {
+            DEBUG(SSSDBG_FATAL_FAILURE, "Could not create request\n");
+            talloc_zfree(tool_ctx);
+            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