[cpp-sp] branch main updated: Draft impl of Curl remoting.

Scott Cantor cantor.2 at osu.edu
Wed Jan 22 17:23:44 UTC 2025


This is an automated email from the git hooks/post-receive script.

scantor pushed a commit to branch main
in repository cpp-sp.

View the commit online:
http://git.shibboleth.net/view/?p=cpp-sp.git;a=commit;h=4472638618543071cb3ae3aa29b63f2a05669ec4

The following commit(s) were added to refs/heads/main by this push:
     new 44726386 Draft impl of Curl remoting.
44726386 is described below

commit 4472638618543071cb3ae3aa29b63f2a05669ec4
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Jan 22 12:23:36 2025 -0500

    Draft impl of Curl remoting.
---
 configure.ac                                       |   2 +
 shibsp/Makefile.am                                 |   1 +
 shibsp/exceptions.h                                |   2 +-
 .../remoting/impl/AbstractHTTPRemotingService.cpp  |   3 +-
 shibsp/remoting/impl/AbstractRemotingService.cpp   |  12 +-
 shibsp/remoting/impl/AbstractRemotingService.h     |  11 +-
 shibsp/remoting/impl/CurlHTTPRemotingService.cpp   | 383 +++++++++++++++++++++
 shibsp/remoting/impl/RemotingService.cpp           |   6 +-
 8 files changed, 410 insertions(+), 10 deletions(-)

diff --git a/configure.ac b/configure.ac
index 45c5f895..d1cbe566 100644
--- a/configure.ac
+++ b/configure.ac
@@ -107,6 +107,8 @@ fi
 AX_PKG_CHECK_MODULES([libcurl],,[libcurl >= 7.4.2],
     [AC_DEFINE([HAVE_LIBCURL],[1],[Define to 1 if libcurl library is available.])])
 
+AC_CHECK_DECLS([CURLOPT_ACCEPT_ENCODING],,,[[#include <curl/curl.h>]])
+
 # Thank you Solaris, really.
 AC_MSG_CHECKING(for ctime_r)
 if test -z "$ac_cv_ctime_args"; then
diff --git a/shibsp/Makefile.am b/shibsp/Makefile.am
index 5b36d307..b31b25b1 100644
--- a/shibsp/Makefile.am
+++ b/shibsp/Makefile.am
@@ -134,6 +134,7 @@ libshibsp_la_SOURCES = \
 	remoting/impl/AbstractRemotingService.cpp \
 	remoting/impl/AbstractHTTPRemotingService.cpp \
 	remoting/impl/SecretSource.cpp \
+	remoting/impl/CurlHTTPRemotingService.cpp \
 	session/impl/AbstractSessionCache.cpp \
 	util/BoostPropertySet.cpp \
 	util/CGIParser.cpp \
diff --git a/shibsp/exceptions.h b/shibsp/exceptions.h
index 1bac5048..712a056f 100644
--- a/shibsp/exceptions.h
+++ b/shibsp/exceptions.h
@@ -131,7 +131,7 @@ namespace shibsp {
     DECL_SHIBSP_EXCEPTION(AttributeException,SHIBSP_EXCEPTIONAPI(SHIBSP_API),shibsp::agent_exception);
     DECL_SHIBSP_EXCEPTION(ConfigurationException,SHIBSP_EXCEPTIONAPI(SHIBSP_API),shibsp::agent_exception);
     DECL_SHIBSP_EXCEPTION(IOException,SHIBSP_EXCEPTIONAPI(SHIBSP_API),shibsp::agent_exception);
-    DECL_SHIBSP_EXCEPTION(RemotintgException,SHIBSP_EXCEPTIONAPI(SHIBSP_API),shibsp::agent_exception);
+    DECL_SHIBSP_EXCEPTION(RemotingException,SHIBSP_EXCEPTIONAPI(SHIBSP_API),shibsp::agent_exception);
     DECL_SHIBSP_EXCEPTION(SessionException,SHIBSP_EXCEPTIONAPI(SHIBSP_API),shibsp::agent_exception);
 
 #if defined (_MSC_VER)
diff --git a/shibsp/remoting/impl/AbstractHTTPRemotingService.cpp b/shibsp/remoting/impl/AbstractHTTPRemotingService.cpp
index a8702a16..65879a53 100644
--- a/shibsp/remoting/impl/AbstractHTTPRemotingService.cpp
+++ b/shibsp/remoting/impl/AbstractHTTPRemotingService.cpp
@@ -45,7 +45,8 @@ const char AbstractHTTPRemotingService::AUTH_METHOD_PROP_DEFAULT[] = "basic";
 unsigned int AbstractHTTPRemotingService::CONNECT_TIMEOUT_PROP_DEFAULT = 3;
 unsigned int AbstractHTTPRemotingService::TIMEOUT_PROP_DEFAULT = 10;
 
-AbstractHTTPRemotingService::AbstractHTTPRemotingService(ptree& pt) : m_authMethod(agent_auth_none)
+AbstractHTTPRemotingService::AbstractHTTPRemotingService(ptree& pt)
+    : AbstractRemotingService(pt), m_authMethod(agent_auth_none)
 {
     BoostPropertySet props;
     props.load(pt);
diff --git a/shibsp/remoting/impl/AbstractRemotingService.cpp b/shibsp/remoting/impl/AbstractRemotingService.cpp
index 32df7c0f..8b76fc9d 100644
--- a/shibsp/remoting/impl/AbstractRemotingService.cpp
+++ b/shibsp/remoting/impl/AbstractRemotingService.cpp
@@ -35,11 +35,13 @@ AbstractRemotingService::~AbstractRemotingService() {}
 
 DDF AbstractRemotingService::send(const DDF& in) const
 {
-    stringstream buf;
-    buf << in;
+    stringstream instream;
+    instream << in;
 
-    DDF output;
-    send(buf) >> output;
+    stringstream outstream;
+    send(in.name(), instream, outstream);
 
+    DDF output;
+    outstream >> output;
     return output;
-}
\ No newline at end of file
+}
diff --git a/shibsp/remoting/impl/AbstractRemotingService.h b/shibsp/remoting/impl/AbstractRemotingService.h
index 83d51828..9e8dbe39 100644
--- a/shibsp/remoting/impl/AbstractRemotingService.h
+++ b/shibsp/remoting/impl/AbstractRemotingService.h
@@ -47,7 +47,16 @@ namespace shibsp {
     protected:
         AbstractRemotingService(const boost::property_tree::ptree& pt);
 
-        virtual std::istream& send(std::istream& input) const=0;
+        /**
+         * Work method for implementations to stream data to hub and
+         * return response stream to read from.
+         * 
+         * @param path  URL path to append to base URL to construct request
+         * @param input input data to stream to hub
+         * @param output output stream to capture response
+         * @return HTTP status code
+         */
+        virtual long send(const char* path, std::istream& input, std::ostream& output) const=0;
     };
 
 };
diff --git a/shibsp/remoting/impl/CurlHTTPRemotingService.cpp b/shibsp/remoting/impl/CurlHTTPRemotingService.cpp
new file mode 100644
index 00000000..dfdb11a2
--- /dev/null
+++ b/shibsp/remoting/impl/CurlHTTPRemotingService.cpp
@@ -0,0 +1,383 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * remoting/impl/CurlHTTPRemotingService.cpp
+ *
+ * Base class for HTTP-based remoting.
+ */
+
+#include "internal.h"
+#include "exceptions.h"
+
+#include "AgentConfig.h"
+#include "logging/Category.h"
+#include "remoting/SecretSource.h"
+#include "remoting/impl/AbstractHTTPRemotingService.h"
+#include "util/BoostPropertySet.h"
+#include "util/PathResolver.h"
+
+#include <stdexcept>
+#include <boost/property_tree/ptree.hpp>
+
+using namespace shibsp;
+using namespace boost::property_tree;
+using namespace std;
+
+#include "internal.h"
+#include "exceptions.h"
+
+#include <list>
+#include <mutex>
+#include <sstream>
+#include <curl/curl.h>
+
+#ifndef HAVE_STRCASECMP
+# define strcasecmp _stricmp
+#endif
+
+namespace {
+
+    /* This is the actual service, and manages the pool of handles for reuse. */
+    class SHIBSP_DLLLOCAL CurlHTTPRemotingService : public virtual AbstractHTTPRemotingService {
+    public:
+        CurlHTTPRemotingService(ptree& pt);
+        virtual ~CurlHTTPRemotingService();
+
+        Category& logger() const {
+            return m_log;
+        }
+
+        Category& curl_logger() const {
+            return m_curllog;
+        }
+
+        bool isChunked() const {
+            return m_chunked;
+        }
+
+        long send(const char* path, istream& input, ostream& output) const;
+
+        CURL* checkout() const;
+        void checkin(CURL* handle) const;
+
+    private:
+        Category& m_log;
+        Category& m_curllog;
+        mutable list<CURL*> m_pool;
+        mutable int m_poolsize;
+        mutable mutex m_lock;
+        string m_useragent;
+        string m_ciphers;
+        string m_cafile;
+        bool m_chunked;
+        bool m_authCaching;
+    };
+
+    class SHIBSP_DLLLOCAL CurlOperation
+    {
+    public:
+        CurlOperation(const CurlHTTPRemotingService& service) : m_service(service), m_handle(nullptr), m_keepHandle(false), m_headers(nullptr) {
+            m_handle = service.checkout();
+            m_headers = curl_slist_append(m_headers, "Content-Type: text/plain");
+            m_headers = curl_slist_append(m_headers, "Expect:");
+        }
+
+        virtual ~CurlOperation() {
+            curl_slist_free_all(m_headers);
+            if (m_keepHandle) {
+                if (curl_easy_setopt(m_handle, CURLOPT_URL, 0) == CURLE_OK &&
+                    curl_easy_setopt(m_handle, CURLOPT_ERRORBUFFER, 0) == CURLE_OK &&
+                    curl_easy_setopt(m_handle, CURLOPT_PASSWORD, 0) == CURLE_OK) {
+                    m_service.checkin(m_handle);
+                    return;
+                }
+            }
+            curl_easy_cleanup(m_handle);
+        }
+
+        string getContentType() const {
+            char* content_type = nullptr;
+            curl_easy_getinfo(m_handle, CURLINFO_CONTENT_TYPE, &content_type);
+            return content_type ? content_type : "";
+        }
+
+        long getStatusCode() const {
+            long code = 200;
+            if (curl_easy_getinfo(m_handle, CURLINFO_RESPONSE_CODE, &code) != CURLE_OK)
+                code = 200;
+            return code;
+        }
+
+
+        bool setRequestHeader(const char* name, const char* val) {
+            string temp(name);
+            temp = temp + ": " + val;
+            m_headers = curl_slist_append(m_headers,temp.c_str());
+            return true;
+        }
+
+        long send(const char* path, istream& in, ostream& out);
+
+    private:
+        // per-call state
+        const CurlHTTPRemotingService& m_service;
+        CURL* m_handle;
+        bool m_keepHandle;
+        struct curl_slist* m_headers;
+		string m_useragent;
+    };
+
+    // callback to send data to server
+    size_t curl_read_hook(void* ptr, size_t size, size_t nmemb, void* stream) {
+        // stream is actually an istream pointer
+        istream* buf=reinterpret_cast<istream*>(stream);
+        buf->read(reinterpret_cast<char*>(ptr), size * nmemb);
+        return buf->gcount();
+    }
+
+    // callback to buffer data from server
+    size_t curl_write_hook(void* ptr, size_t size, size_t nmemb, void* stream) {
+        size_t len = size * nmemb;
+        reinterpret_cast<ostream*>(stream)->write(reinterpret_cast<const char*>(ptr), len);
+        return len;
+    }
+
+    // callback for curl debug data
+    int curl_debug_hook(CURL* handle, curl_infotype type, char* data, size_t len, void* ptr) {
+        if (ptr) {
+            // *ptr is actually a logging object
+            string buf;
+            for (unsigned char* ch = (unsigned char*)data; len && (isprint(*ch) || isspace(*ch)); len--) {
+                buf += *ch++;
+            }
+            reinterpret_cast<Category*>(ptr)->debug(buf);
+        }
+        return 0;
+    }
+    
+};
+
+namespace shibsp {
+    RemotingService* CurlHTTPRemotingServiceFactory(ptree& pt, bool deprecationSupport) {
+        return new CurlHTTPRemotingService(pt);
+    }
+};
+
+CurlHTTPRemotingService::CurlHTTPRemotingService(ptree& pt)
+    : AbstractHTTPRemotingService(pt), AbstractRemotingService(pt),
+        m_log(Category::getInstance(SHIBSP_LOGCAT ".RemotingService.CurlHTTP")),
+            m_curllog(Category::getInstance(SHIBSP_LOGCAT ".libcurl")),
+                m_poolsize(20), m_chunked(true), m_authCaching(true)
+{
+    static const char USER_AGENT_PROP_NAME[] = "userAgentString";
+    static const char CIPHER_LIST_PROP_NAME[] = "tlsCipherList";
+    static const char CHUNKED_PROP_NAME[] = "chunkedEncoding";
+    static const char ENABLE_AUTH_CACHING[] = "enableAuthCaching";
+    static const char CA_FILE_PROP_NAME[] = "tlsCAFile";
+    static const char CA_FILE_PROP_DEFAULT[] = "trustlist.pem";
+
+    BoostPropertySet props;
+    props.load(pt);
+
+    m_useragent = props.getString(USER_AGENT_PROP_NAME, "");
+    m_chunked = props.getBool(CHUNKED_PROP_NAME, true);
+    m_authCaching = props.getBool(ENABLE_AUTH_CACHING, true);
+    m_ciphers = props.getString(CIPHER_LIST_PROP_NAME, "");
+    m_cafile = props.getString(CA_FILE_PROP_NAME, CA_FILE_PROP_DEFAULT);
+    if (!m_cafile.empty()) {
+        AgentConfig::getConfig().getPathResolver().resolve(m_cafile, PathResolver::SHIBSP_CFG_FILE);
+    }
+
+    if (m_useragent.empty()) {
+        m_useragent = m_useragent + PACKAGE_NAME + '/' + PACKAGE_VERSION;
+        curl_version_info_data* curlver = curl_version_info(CURLVERSION_NOW);
+        if (curlver) {
+            m_useragent = m_useragent + " libcurl/" + curlver->version + ' ' + curlver->ssl_version;
+        }
+    }
+}
+
+CurlHTTPRemotingService::~CurlHTTPRemotingService()
+{
+    for (CURL* handle : m_pool) {
+        curl_easy_cleanup(handle);
+    }
+    m_pool.clear();
+}
+
+#define SHIB_CURL_SET(opt, val) \
+    if (curl_easy_setopt(m_handle, opt, val) != CURLE_OK) { \
+        curl_easy_cleanup(m_handle); \
+        throw RemotingException("Failed to set "#opt) ; \
+    }
+
+CURL* CurlHTTPRemotingService::checkout() const
+{
+    m_log.debug("getting connection handle");
+
+    m_lock.lock();
+
+    // If a free connection exists, return it.
+    if (!m_pool.empty()) {
+        CURL* handle = m_pool.back();
+        m_pool.pop_back();
+        m_poolsize--;
+        m_lock.unlock();
+        m_log.debug("returning existing connection handle from pool");
+        return handle;
+    }
+
+    m_lock.unlock();
+    m_log.debug("nothing free in pool, returning new connection handle");
+
+    // Create a new connection and set non-varying options.
+    CURL* m_handle = curl_easy_init();
+    if (!m_handle) {
+        return nullptr;
+    }
+
+    SHIB_CURL_SET(CURLOPT_NOPROGRESS, 1);
+    SHIB_CURL_SET(CURLOPT_NOSIGNAL, 1);
+    SHIB_CURL_SET(CURLOPT_FAILONERROR, 1);
+    SHIB_CURL_SET(CURLOPT_PROTOCOLS_STR, "http,https");
+    SHIB_CURL_SET(CURLOPT_FOLLOWLOCATION, 0);
+#if HAVE_DECL_CURLOPT_ACCEPT_ENCODING
+    SHIB_CURL_SET(CURLOPT_ACCEPT_ENCODING, "");
+#else
+    SHIB_CURL_SET(CURLOPT_ENCODING, "");
+#endif
+    SHIB_CURL_SET(CURLOPT_USERAGENT, m_useragent.c_str());
+
+    // This may (but probably won't) help with < 7.20 bug in DNS caching.
+    SHIB_CURL_SET(CURLOPT_DNS_CACHE_TIMEOUT, 120);
+
+    SHIB_CURL_SET(CURLOPT_SSL_VERIFYPEER, 1);
+    SHIB_CURL_SET(CURLOPT_SSL_VERIFYHOST, 2);
+    if (!m_ciphers.empty()) {
+        SHIB_CURL_SET(CURLOPT_SSL_CIPHER_LIST, m_ciphers.c_str());
+    }
+    if (!m_cafile.empty()) {
+        SHIB_CURL_SET(CURLOPT_CAINFO, m_cafile.c_str());
+    }
+
+    SHIB_CURL_SET(CURLOPT_CONNECTTIMEOUT, getConnectTimeout());
+    SHIB_CURL_SET(CURLOPT_TIMEOUT, getTimeout());
+
+    long flag=0;
+    switch (getAuthMethod()) {
+        case agent_auth_basic:  flag = CURLAUTH_BASIC; break;
+        case agent_auth_digest: flag = CURLAUTH_DIGEST; break;
+        case agent_auth_gss:    flag = CURLAUTH_NEGOTIATE; break;
+        case agent_auth_none:
+        default:                flag = 0; break;
+    }
+    SHIB_CURL_SET(CURLOPT_HTTPAUTH, flag);
+    // Password will be acquired during call.
+    SHIB_CURL_SET(CURLOPT_USERNAME, getAgentID());
+
+    SHIB_CURL_SET(CURLOPT_WRITEFUNCTION, &curl_write_hook);
+    SHIB_CURL_SET(CURLOPT_DEBUGFUNCTION, &curl_debug_hook);
+    SHIB_CURL_SET(CURLOPT_DEBUGDATA, &m_curllog);
+    return m_handle;
+}
+
+void CurlHTTPRemotingService::checkin(CURL* handle) const
+{
+    m_lock.lock();
+    m_pool.push_back(handle);
+
+    CURL* killit=nullptr;
+    if (++m_poolsize > 256) {
+        // Grab and dispose of the "front" element.
+        killit = m_pool.front();
+        m_pool.pop_front();
+        m_poolsize--;
+    }
+    m_lock.unlock();
+
+    if (killit) {
+        curl_easy_cleanup(killit);
+        m_log.info("conn_pool_max limit reached, dropping an old connection");
+    }
+}
+
+long CurlHTTPRemotingService::send(const char* path, istream& input, ostream& output) const
+{
+    CurlOperation op(*this);
+    return op.send(path, input, output);
+}
+
+long CurlOperation::send(const char* path, istream& in, ostream& out)
+{
+    // Append call path to base URL.
+    string url(m_service.getBaseURL());
+    if (path) {
+        url += path;
+    }
+    SHIB_CURL_SET(CURLOPT_URL, url.c_str());
+
+    if (m_service.getAuthMethod() == CurlHTTPRemotingService::agent_auth_basic ||
+        m_service.getAuthMethod() == CurlHTTPRemotingService::agent_auth_digest) {
+        SHIB_CURL_SET(CURLOPT_PASSWORD, m_service.getSecretSource()->getSecret().c_str());
+    }
+
+    string msg;
+
+    // Setup standard per-call curl properties.
+    if (m_service.curl_logger().isDebugEnabled()) {
+        curl_easy_setopt(m_handle, CURLOPT_VERBOSE, 1);
+    }
+
+    SHIB_CURL_SET(CURLOPT_WRITEDATA, &out);
+    if (m_service.isChunked()) {
+        SHIB_CURL_SET(CURLOPT_POST, 1);
+        m_headers = curl_slist_append(m_headers, "Transfer-Encoding: chunked");
+        SHIB_CURL_SET(CURLOPT_READFUNCTION, &curl_read_hook);
+        SHIB_CURL_SET(CURLOPT_READDATA, &in);
+    }
+    else {
+        char buf[1024];
+        while (in) {
+            in.read(buf, 1024);
+            msg.append(buf, in.gcount());
+        }
+        SHIB_CURL_SET(CURLOPT_POST, 1);
+        SHIB_CURL_SET(CURLOPT_READFUNCTION, 0);
+        SHIB_CURL_SET(CURLOPT_POSTFIELDS, msg.c_str());
+        SHIB_CURL_SET(CURLOPT_POSTFIELDSIZE, msg.length());
+    }
+
+    char curl_errorbuf[CURL_ERROR_SIZE];
+    curl_errorbuf[0] = 0;
+    SHIB_CURL_SET(CURLOPT_ERRORBUFFER, curl_errorbuf);
+
+    // Set request headers.
+    SHIB_CURL_SET(CURLOPT_HTTPHEADER, m_headers);
+
+    // Make the call.
+    m_service.logger().debug("sending request to %s", url.c_str());
+    CURLcode code = curl_easy_perform(m_handle);
+
+    if (code != CURLE_OK) {
+        throw RemotingException("Remote request failed at " + url + ": " +
+            (curl_errorbuf[0] ? curl_errorbuf : "no further information available"));
+    }
+
+    // This won't prevent every possible failed connection from being kept, but it's something.
+    m_keepHandle = true;
+
+    return getStatusCode();
+}
+
diff --git a/shibsp/remoting/impl/RemotingService.cpp b/shibsp/remoting/impl/RemotingService.cpp
index 7090ecc3..fccf4894 100644
--- a/shibsp/remoting/impl/RemotingService.cpp
+++ b/shibsp/remoting/impl/RemotingService.cpp
@@ -29,17 +29,19 @@ using namespace boost::property_tree;
 using namespace std;
 
 namespace shibsp {
-    extern RemotingService* SHIBSP_DLLLOCAL CurlHTTPRemotingServiceFactory(ptree& pt, bool deprecationSupport);
 #ifdef WIN32
     extern RemotingService* SHIBSP_DLLLOCAL WinHTTPRemotingServiceFactory(ptree& pt, bool deprecationSupport);
+#else
+    extern RemotingService* SHIBSP_DLLLOCAL CurlHTTPRemotingServiceFactory(ptree& pt, bool deprecationSupport);
 #endif
 };
 
 void SHIBSP_API shibsp::registerRemotingServices()
 {
-    //AgentConfig::getConfig().RemotingServiceManager.registerFactory(CURL_HTTP_REMOTING_SERVICE, CurlHTTPRemotingServiceFactory);
 #ifdef WIN32
     //AgentConfig::getConfig().RemotingServiceManager.registerFactory(WIN_HTTP_REMOTING_SERVICE, WinHTTPRemotingServiceFactory);
+#else
+    AgentConfig::getConfig().RemotingServiceManager.registerFactory(CURL_HTTP_REMOTING_SERVICE, CurlHTTPRemotingServiceFactory);
 #endif
 }
 

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list