[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