Move credential usage enum to Credential class.
[shibboleth/xmltooling.git] / xmltooling / soap / impl / CURLSOAPTransport.cpp
index da8a3f5..6f8f11f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2001-2006 Internet2
+ *  Copyright 2001-2007 Internet2
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 #include "internal.h"
 #include "exceptions.h"
+#include "logging.h"
+#include "security/CredentialCriteria.h"
 #include "security/OpenSSLTrustEngine.h"
-#include "signature/OpenSSLCredentialResolver.h"
+#include "security/OpenSSLCredential.h"
 #include "soap/HTTPSOAPTransport.h"
 #include "soap/OpenSSLSOAPTransport.h"
 #include "util/NDC.h"
 
 #include <list>
 #include <curl/curl.h>
-#include <log4cpp/Category.hh>
 #include <openssl/x509_vfy.h>
 
-using namespace xmlsignature;
+using namespace xmltooling::logging;
 using namespace xmltooling;
-using namespace log4cpp;
 using namespace std;
 
 namespace xmltooling {
@@ -45,12 +45,12 @@ namespace xmltooling {
     class XMLTOOL_DLLLOCAL CURLPool
     {
     public:
-        CURLPool() : m_size(256), m_lock(Mutex::create()),
+        CURLPool() : m_size(0), m_lock(Mutex::create()),
             m_log(Category::getInstance(XMLTOOLING_LOGCAT".SOAPTransport.CURLPool")) {}
         ~CURLPool();
         
-        CURL* get(const string& to, const char* endpoint);
-        void put(const string& to, const char* endpoint, CURL* handle);
+        CURL* get(const SOAPTransport::Address& addr);
+        void put(const char* from, const char* to, const char* endpoint, CURL* handle);
     
     private:    
         typedef map<string,vector<CURL*> > poolmap_t;
@@ -66,16 +66,20 @@ namespace xmltooling {
     class XMLTOOL_DLLLOCAL CURLSOAPTransport : public HTTPSOAPTransport, public OpenSSLSOAPTransport
     {
     public:
-        CURLSOAPTransport(const KeyInfoSource& peer, const char* endpoint)
-                : m_peer(peer), m_endpoint(endpoint), m_handle(NULL), m_headers(NULL),
-                    m_credResolver(NULL), m_trustEngine(NULL), m_keyResolver(NULL),
-                    m_ssl_callback(NULL), m_ssl_userptr(NULL) {
-            m_handle = g_CURLPool->get(peer.getName(), endpoint);
-            curl_easy_setopt(m_handle,CURLOPT_URL,endpoint);
+        CURLSOAPTransport(const Address& addr)
+            : m_sender(addr.m_from ? addr.m_from : ""), m_peerName(addr.m_to ? addr.m_to : ""), m_endpoint(addr.m_endpoint),
+                m_handle(NULL), m_headers(NULL),
+#ifndef XMLTOOLING_NO_XMLSEC
+                    m_cred(NULL), m_trustEngine(NULL), m_peerResolver(NULL), m_mandatory(false),
+#endif
+                    m_ssl_callback(NULL), m_ssl_userptr(NULL), m_chunked(true), m_authenticated(false) {
+            m_handle = g_CURLPool->get(addr);
+            curl_easy_setopt(m_handle,CURLOPT_URL,addr.m_endpoint);
             curl_easy_setopt(m_handle,CURLOPT_CONNECTTIMEOUT,15);
             curl_easy_setopt(m_handle,CURLOPT_TIMEOUT,30);
             curl_easy_setopt(m_handle,CURLOPT_HTTPAUTH,0);
             curl_easy_setopt(m_handle,CURLOPT_USERPWD,NULL);
+            curl_easy_setopt(m_handle,CURLOPT_SSL_VERIFYHOST,2);
             curl_easy_setopt(m_handle,CURLOPT_HEADERDATA,this);
             m_headers=curl_slist_append(m_headers,"Content-Type: text/xml");
         }
@@ -83,50 +87,97 @@ namespace xmltooling {
         virtual ~CURLSOAPTransport() {
             curl_slist_free_all(m_headers);
             curl_easy_setopt(m_handle,CURLOPT_ERRORBUFFER,NULL);
-            g_CURLPool->put(m_peer.getName(), m_endpoint.c_str(), m_handle);
+            curl_easy_setopt(m_handle,CURLOPT_PRIVATE,m_authenticated ? "secure" : NULL); // Save off security "state".
+            g_CURLPool->put(m_sender.c_str(), m_peerName.c_str(), m_endpoint.c_str(), m_handle);
+        }
+
+        bool isConfidential() const {
+            return m_endpoint.find("https")==0;
         }
 
-        bool setConnectTimeout(long timeout) const {
+        bool setConnectTimeout(long timeout) {
             return (curl_easy_setopt(m_handle,CURLOPT_CONNECTTIMEOUT,timeout)==CURLE_OK);
         }
         
-        bool setTimeout(long timeout) const {
+        bool setTimeout(long timeout) {
             return (curl_easy_setopt(m_handle,CURLOPT_TIMEOUT,timeout)==CURLE_OK);
         }
         
-        bool setAuth(transport_auth_t authType, const char* username=NULL, const char* password=NULL) const;
+        bool setAuth(transport_auth_t authType, const char* username=NULL, const char* password=NULL);
         
-        bool setCredentialResolver(const CredentialResolver* credResolver) const {
-            const OpenSSLCredentialResolver* down = dynamic_cast<const OpenSSLCredentialResolver*>(credResolver);
+        bool setVerifyHost(bool verify) {
+            return (curl_easy_setopt(m_handle,CURLOPT_SSL_VERIFYHOST,verify ? 2 : 0)==CURLE_OK);
+        }
+        
+#ifndef XMLTOOLING_NO_XMLSEC
+        bool setCredential(const Credential* cred=NULL) {
+            const OpenSSLCredential* down = dynamic_cast<const OpenSSLCredential*>(cred);
             if (!down) {
-                m_credResolver = NULL;
-                return (credResolver==NULL);
+                m_cred = NULL;
+                return (cred==NULL);
             }
-            m_credResolver = down;
+            m_cred = down;
             return true;
         }
         
-        bool setTrustEngine(const X509TrustEngine* trustEngine, const KeyResolver* keyResolver=NULL) const {
+        bool setTrustEngine(
+            const X509TrustEngine* trustEngine=NULL,
+            const CredentialResolver* peerResolver=NULL,
+            CredentialCriteria* criteria=NULL,
+            bool mandatory=true
+            ) {
             const OpenSSLTrustEngine* down = dynamic_cast<const OpenSSLTrustEngine*>(trustEngine);
             if (!down) {
                 m_trustEngine = NULL;
-                m_keyResolver = NULL;
+                m_peerResolver = NULL;
+                m_criteria = NULL;
                 return (trustEngine==NULL);
             }
             m_trustEngine = down;
-            m_keyResolver = keyResolver;
+            m_peerResolver = peerResolver;
+            m_criteria = criteria;
+            m_mandatory = mandatory;
             return true;
         }
         
+#endif
+        
+        bool useChunkedEncoding(bool chunked=true) {
+            m_chunked = chunked;
+            return true;
+        }
+
+        bool setProviderOption(const char* provider, const char* option, const char* value) {
+            if (!provider || strcmp(provider, "CURL"))
+                return false;
+            // For libcurl, the option is an enum and the value type depends on the option.
+            CURLoption opt = static_cast<CURLoption>(strtol(option, NULL, 10));
+            if (opt < CURLOPTTYPE_OBJECTPOINT)
+                return (curl_easy_setopt(m_handle, opt, strtol(value, NULL, 10)) == CURLE_OK);
+            else if (opt < CURLOPTTYPE_OFF_T)
+                return (curl_easy_setopt(m_handle, opt, value) == CURLE_OK);
+            else if (sizeof(curl_off_t) == sizeof(long))
+                return (curl_easy_setopt(m_handle, opt, strtol(value, NULL, 10)) == CURLE_OK);
+            return false;
+        }
+        
         void send(istream& in);
         
         istream& receive() {
             return m_stream;
         }
         
+        bool isAuthenticated() const {
+            return m_authenticated;
+        }
+
+        void setAuthenticated(bool auth) {
+            m_authenticated = auth;
+        }
+
         string getContentType() const;
         
-        bool setRequestHeader(const char* name, const char* val) const {
+        bool setRequestHeader(const char* name, const char* val) {
             string temp(name);
             temp=temp + ": " + val;
             m_headers=curl_slist_append(m_headers,temp.c_str());
@@ -135,7 +186,7 @@ namespace xmltooling {
         
         const vector<string>& getResponseHeader(const char* val) const;
         
-        bool setSSLCallback(ssl_ctx_callback_fn fn, void* userptr=NULL) const {
+        bool setSSLCallback(ssl_ctx_callback_fn fn, void* userptr=NULL) {
             m_ssl_callback=fn;
             m_ssl_userptr=userptr;
             return true;
@@ -143,17 +194,22 @@ namespace xmltooling {
 
     private:        
         // per-call state
-        const KeyInfoSource& m_peer;
-        string m_endpoint;
+        string m_sender,m_peerName,m_endpoint,m_simplecreds;
         CURL* m_handle;
         stringstream m_stream;
-        mutable struct curl_slist* m_headers;
+        struct curl_slist* m_headers;
         map<string,vector<string> > m_response_headers;
-        mutable const OpenSSLCredentialResolver* m_credResolver;
-        mutable const OpenSSLTrustEngine* m_trustEngine;
-        mutable const KeyResolver* m_keyResolver;
-        mutable ssl_ctx_callback_fn m_ssl_callback;
-        mutable void* m_ssl_userptr;
+#ifndef XMLTOOLING_NO_XMLSEC
+        const OpenSSLCredential* m_cred;
+        const OpenSSLTrustEngine* m_trustEngine;
+        const CredentialResolver* m_peerResolver;
+        CredentialCriteria* m_criteria;
+        bool m_mandatory;
+#endif
+        ssl_ctx_callback_fn m_ssl_callback;
+        void* m_ssl_userptr;
+        bool m_chunked;
+        bool m_authenticated;
         
         friend size_t XMLTOOL_DLLLOCAL curl_header_hook(void* ptr, size_t size, size_t nmemb, void* stream);
         friend CURLcode XMLTOOL_DLLLOCAL xml_ssl_ctx_callback(CURL* curl, SSL_CTX* ssl_ctx, void* userptr);
@@ -166,11 +222,13 @@ namespace xmltooling {
     size_t XMLTOOL_DLLLOCAL curl_read_hook( void *ptr, size_t size, size_t nmemb, void *stream);
     int XMLTOOL_DLLLOCAL curl_debug_hook(CURL* handle, curl_infotype type, char* data, size_t len, void* ptr);
     CURLcode XMLTOOL_DLLLOCAL xml_ssl_ctx_callback(CURL* curl, SSL_CTX* ssl_ctx, void* userptr);
+#ifndef XMLTOOLING_NO_XMLSEC
     int XMLTOOL_DLLLOCAL verify_callback(X509_STORE_CTX* x509_ctx, void* arg);
+#endif
 
-    SOAPTransport* CURLSOAPTransportFactory(const pair<const KeyInfoSource*,const char*>& dest)
+    SOAPTransport* CURLSOAPTransportFactory(const SOAPTransport::Address& addr)
     {
-        return new CURLSOAPTransport(*dest.first, dest.second);
+        return new CURLSOAPTransport(addr);
     }
 };
 
@@ -201,14 +259,19 @@ CURLPool::~CURLPool()
     delete m_lock;
 }
 
-CURL* CURLPool::get(const string& to, const char* endpoint)
+CURL* CURLPool::get(const SOAPTransport::Address& addr)
 {
 #ifdef _DEBUG
     xmltooling::NDC("get");
 #endif
-    m_log.debug("getting connection handle to %s", endpoint);
+    m_log.debug("getting connection handle to %s", addr.m_endpoint);
+    string key(addr.m_endpoint);
+    if (addr.m_from)
+        key = key + '|' + addr.m_from;
+    if (addr.m_to)
+        key = key + '|' + addr.m_to;
     m_lock->lock();
-    poolmap_t::iterator i=m_bindingMap.find(to + "|" + endpoint);
+    poolmap_t::iterator i=m_bindingMap.find(key);
     
     if (i!=m_bindingMap.end()) {
         // Move this pool to the front of the list.
@@ -236,19 +299,24 @@ CURL* CURLPool::get(const string& to, const char* endpoint)
     curl_easy_setopt(handle,CURLOPT_NOPROGRESS,1);
     curl_easy_setopt(handle,CURLOPT_NOSIGNAL,1);
     curl_easy_setopt(handle,CURLOPT_FAILONERROR,1);
-    curl_easy_setopt(handle,CURLOPT_SSLVERSION,3);
-    curl_easy_setopt(handle,CURLOPT_SSL_VERIFYHOST,2);
+    curl_easy_setopt(handle,CURLOPT_SSLVERSION,CURL_SSLVERSION_SSLv3);
+    curl_easy_setopt(handle,CURLOPT_SSL_CIPHER_LIST,"ALL:!aNULL:!LOW:!EXPORT:!SSLv2");
+    // Verification of the peer is via TrustEngine only.
+    curl_easy_setopt(handle,CURLOPT_SSL_VERIFYPEER,0);
     curl_easy_setopt(handle,CURLOPT_HEADERFUNCTION,&curl_header_hook);
-    curl_easy_setopt(handle,CURLOPT_READFUNCTION,&curl_read_hook);
     curl_easy_setopt(handle,CURLOPT_WRITEFUNCTION,&curl_write_hook);
     curl_easy_setopt(handle,CURLOPT_DEBUGFUNCTION,&curl_debug_hook);
 
     return handle;
 }
 
-void CURLPool::put(const string& to, const char* endpoint, CURL* handle)
+void CURLPool::put(const char* from, const char* to, const char* endpoint, CURL* handle)
 {
-    string key = to + "|" + endpoint;
+    string key(endpoint);
+    if (from)
+        key = key + '|' + from;
+    if (to)
+        key = key + '|' + to;
     m_lock->lock();
     poolmap_t::iterator i=m_bindingMap.find(key);
     if (i==m_bindingMap.end())
@@ -283,7 +351,7 @@ void CURLPool::put(const string& to, const char* endpoint, CURL* handle)
     }
 }
 
-bool CURLSOAPTransport::setAuth(transport_auth_t authType, const char* username, const char* password) const
+bool CURLSOAPTransport::setAuth(transport_auth_t authType, const char* username, const char* password)
 {
     if (authType==transport_auth_none) {
         if (curl_easy_setopt(m_handle,CURLOPT_HTTPAUTH,0)!=CURLE_OK)
@@ -300,8 +368,8 @@ bool CURLSOAPTransport::setAuth(transport_auth_t authType, const char* username,
     }
     if (curl_easy_setopt(m_handle,CURLOPT_HTTPAUTH,flag)!=CURLE_OK)
         return false;
-    string creds = string(username ? username : "") + ':' + (password ? password : "");
-    return (curl_easy_setopt(m_handle,CURLOPT_USERPWD,creds.c_str())==CURLE_OK);
+    m_simplecreds = string(username ? username : "") + ':' + (password ? password : "");
+    return (curl_easy_setopt(m_handle,CURLOPT_USERPWD,m_simplecreds.c_str())==CURLE_OK);
 }
 
 const vector<string>& CURLSOAPTransport::getResponseHeader(const char* name) const
@@ -339,14 +407,30 @@ void CURLSOAPTransport::send(istream& in)
     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".SOAPTransport");
     Category& log_curl=Category::getInstance(XMLTOOLING_LOGCAT".libcurl");
 
+    string msg;
+
     // By this time, the handle has been prepared with the URL to use and the
     // caller should have executed any set functions to manipulate it.
 
     // Setup standard per-call curl properties.
-    curl_easy_setopt(m_handle,CURLOPT_POST,1);
-    curl_easy_setopt(m_handle,CURLOPT_READDATA,&in);
-    curl_easy_setopt(m_handle,CURLOPT_FILE,&m_stream);
     curl_easy_setopt(m_handle,CURLOPT_DEBUGDATA,&log_curl);
+    curl_easy_setopt(m_handle,CURLOPT_FILE,&m_stream);
+    curl_easy_setopt(m_handle,CURLOPT_POST,1);
+    if (m_chunked) {
+        m_headers=curl_slist_append(m_headers,"Transfer-Encoding: chunked");
+        curl_easy_setopt(m_handle,CURLOPT_READFUNCTION,&curl_read_hook);
+        curl_easy_setopt(m_handle,CURLOPT_READDATA,&in);
+    }
+    else {
+        char buf[1024];
+        while (in) {
+            in.read(buf,1024);
+            msg.append(buf,in.gcount());
+        }
+        curl_easy_setopt(m_handle,CURLOPT_READFUNCTION,NULL);
+        curl_easy_setopt(m_handle,CURLOPT_POSTFIELDS,msg.c_str());
+        curl_easy_setopt(m_handle,CURLOPT_POSTFIELDSIZE,msg.length());
+    }
 
     char curl_errorbuf[CURL_ERROR_SIZE];
     curl_errorbuf[0]=0;
@@ -354,28 +438,34 @@ void CURLSOAPTransport::send(istream& in)
     if (log_curl.isDebugEnabled())
         curl_easy_setopt(m_handle,CURLOPT_VERBOSE,1);
 
-    // Set request headers (possibly appended by hooks).
+    // Set request headers.
     curl_easy_setopt(m_handle,CURLOPT_HTTPHEADER,m_headers);
 
-    if (m_ssl_callback || m_credResolver || m_trustEngine) {
+#ifndef XMLTOOLING_NO_XMLSEC
+    if (m_ssl_callback || m_cred || m_trustEngine) {
+#else
+    if (m_ssl_callback) {
+#endif
         curl_easy_setopt(m_handle,CURLOPT_SSL_CTX_FUNCTION,xml_ssl_ctx_callback);
         curl_easy_setopt(m_handle,CURLOPT_SSL_CTX_DATA,this);
+
+        // Restore security "state". Necessary because the callback only runs
+        // when handshakes occur. Even new TCP connections won't execute it.
+        char* priv=NULL;
+        curl_easy_getinfo(m_handle,CURLINFO_PRIVATE,&priv);
+        if (priv)
+            m_authenticated=true;
     }
     else {
         curl_easy_setopt(m_handle,CURLOPT_SSL_CTX_FUNCTION,NULL);
         curl_easy_setopt(m_handle,CURLOPT_SSL_CTX_DATA,NULL);
     }
     
-    // Verification of the peer is via TrustEngine only.
-    curl_easy_setopt(m_handle,CURLOPT_SSL_VERIFYPEER,0);
-
     // Make the call.
-    log.info("sending SOAP message to %s", m_endpoint.c_str());
+    log.debug("sending SOAP message to %s", m_endpoint.c_str());
     if (curl_easy_perform(m_handle) != CURLE_OK) {
-        log.error("failed communicating with SOAP endpoint: %s",
-            (curl_errorbuf[0] ? curl_errorbuf : "no further information available"));
         throw IOException(
-            string("CURLSOAPTransport::send() failed while contacting SOAP responder: ") +
+            string("CURLSOAPTransport failed while contacting SOAP responder: ") +
                 (curl_errorbuf[0] ? curl_errorbuf : "no further information available"));
     }
 }
@@ -430,15 +520,16 @@ int xmltooling::curl_debug_hook(CURL* handle, curl_infotype type, char* data, si
     // *ptr is actually a logging object
     if (!ptr) return 0;
     CategoryStream log=reinterpret_cast<Category*>(ptr)->debugStream();
-    for (char* ch=data; len && (isprint(*ch) || isspace(*ch)); len--)
+    for (unsigned char* ch=(unsigned char*)data; len && (isprint(*ch) || isspace(*ch)); len--)
         log << *ch++;
-    log << CategoryStream::ENDLINE;
     return 0;
 }
 
+#ifndef XMLTOOLING_NO_XMLSEC
 int xmltooling::verify_callback(X509_STORE_CTX* x509_ctx, void* arg)
 {
-    Category::getInstance("OpenSSL").debug("invoking X509 verify callback");
+    Category& log = Category::getInstance("OpenSSL");
+    log.debug("invoking X509 verify callback");
 #if (OPENSSL_VERSION_NUMBER >= 0x00907000L)
     CURLSOAPTransport* ctx = reinterpret_cast<CURLSOAPTransport*>(arg);
 #else
@@ -453,22 +544,41 @@ int xmltooling::verify_callback(X509_STORE_CTX* x509_ctx, void* arg)
         );
 #endif
 
-     // Bypass name check (handled for us by curl).
-    if (!ctx->m_trustEngine->validate(x509_ctx->cert,x509_ctx->untrusted,ctx->m_peer,false,ctx->m_keyResolver)) {
+    bool success=false;
+    if (ctx->m_criteria) {
+        ctx->m_criteria->setUsage(Credential::TLS_CREDENTIAL);
+        // Bypass name check (handled for us by curl).
+        ctx->m_criteria->setPeerName(NULL);
+        success = ctx->m_trustEngine->validate(x509_ctx->cert,x509_ctx->untrusted,*(ctx->m_peerResolver),ctx->m_criteria);
+    }
+    else {
+        // Bypass name check (handled for us by curl).
+        CredentialCriteria cc;
+        cc.setUsage(Credential::TLS_CREDENTIAL);
+        success = ctx->m_trustEngine->validate(x509_ctx->cert,x509_ctx->untrusted,*(ctx->m_peerResolver),&cc);
+    }
+    
+    if (!success) {
+        log.error("supplied TrustEngine failed to validate SSL/TLS server certificate");
         x509_ctx->error=X509_V_ERR_APPLICATION_VERIFICATION;     // generic error, check log for plugin specifics
-        return 0;
+        ctx->setAuthenticated(false);
+        return ctx->m_mandatory ? 0 : 1;
     }
     
     // Signal success. Hopefully it doesn't matter what's actually in the structure now.
+    ctx->setAuthenticated(true);
     return 1;
 }
+#endif
 
 // callback to invoke a caller-defined SSL callback
 CURLcode xmltooling::xml_ssl_ctx_callback(CURL* curl, SSL_CTX* ssl_ctx, void* userptr)
 {
     CURLSOAPTransport* conf = reinterpret_cast<CURLSOAPTransport*>(userptr);
-    if (conf->m_credResolver)
-        conf->m_credResolver->attach(ssl_ctx);
+
+#ifndef XMLTOOLING_NO_XMLSEC
+    if (conf->m_cred)
+        conf->m_cred->attach(ssl_ctx);
 
     if (conf->m_trustEngine) {
         SSL_CTX_set_verify(ssl_ctx,SSL_VERIFY_PEER,NULL);
@@ -483,8 +593,9 @@ CURLcode xmltooling::xml_ssl_ctx_callback(CURL* curl, SSL_CTX* ssl_ctx, void* us
         SSL_CTX_set_verify_depth(ssl_ctx,reinterpret_cast<int>(userptr));
 #endif
     }
+#endif
         
-    if (!conf->m_ssl_callback(ssl_ctx,conf->m_ssl_userptr))
+    if (conf->m_ssl_callback && !conf->m_ssl_callback(conf, ssl_ctx, conf->m_ssl_userptr))
         return CURLE_SSL_CERTPROBLEM;
         
     return CURLE_OK;