4f8a3c4b7e99579d639169ce3f01cd7b07fca223
[shibboleth/cpp-xmltooling.git] / xmltooling / soap / impl / CURLSOAPTransport.cpp
1 /*
2  * Licensed to UCAID under one or more contributor license agreements.
3  * See the NOTICE file distributed with this work for additional information
4  * regarding copyright ownership. The ASF licenses this file to you under
5  * the Apache License, Version 2.0 (the "License"); you may not use this
6  * file except in compliance with the License.  You may obtain a copy of the
7  * License at
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 /**
19  * CURLSOAPTransport.cpp
20  *
21  * libcurl-based SOAPTransport implementation.
22  */
23
24 #include "internal.h"
25 #include "exceptions.h"
26 #include "logging.h"
27 #include "security/CredentialCriteria.h"
28 #include "security/OpenSSLTrustEngine.h"
29 #include "security/OpenSSLCredential.h"
30 #include "soap/HTTPSOAPTransport.h"
31 #include "soap/OpenSSLSOAPTransport.h"
32 #include "util/NDC.h"
33 #include "util/Threads.h"
34
35 #include <list>
36 #include <curl/curl.h>
37 #include <openssl/x509_vfy.h>
38
39 using namespace xmltooling::logging;
40 using namespace xmltooling;
41 using namespace std;
42
43 namespace xmltooling {
44
45     // Manages cache of socket connections via CURL handles.
46     class XMLTOOL_DLLLOCAL CURLPool
47     {
48     public:
49         CURLPool() : m_size(0), m_lock(Mutex::create()),
50             m_log(Category::getInstance(XMLTOOLING_LOGCAT".SOAPTransport.CURL")) {}
51         ~CURLPool();
52
53         CURL* get(const SOAPTransport::Address& addr);
54         void put(const char* from, const char* to, const char* endpoint, CURL* handle);
55
56     private:
57         typedef map<string,vector<CURL*> > poolmap_t;
58         poolmap_t m_bindingMap;
59         list< vector<CURL*>* > m_pools;
60         long m_size;
61         Mutex* m_lock;
62         Category& m_log;
63     };
64
65     static XMLTOOL_DLLLOCAL CURLPool* g_CURLPool = nullptr;
66
67     class XMLTOOL_DLLLOCAL CURLSOAPTransport : public HTTPSOAPTransport, public OpenSSLSOAPTransport
68     {
69     public:
70         CURLSOAPTransport(const Address& addr)
71             : m_sender(addr.m_from ? addr.m_from : ""), m_peerName(addr.m_to ? addr.m_to : ""), m_endpoint(addr.m_endpoint),
72                 m_handle(nullptr), m_headers(nullptr),
73 #ifndef XMLTOOLING_NO_XMLSEC
74                     m_cred(nullptr), m_trustEngine(nullptr), m_peerResolver(nullptr), m_mandatory(false),
75 #endif
76                     m_openssl_ops(SSL_OP_ALL|SSL_OP_NO_SSLv2), m_ssl_callback(nullptr), m_ssl_userptr(nullptr),
77                     m_chunked(true), m_authenticated(false), m_cacheTag(nullptr) {
78             m_handle = g_CURLPool->get(addr);
79             curl_easy_setopt(m_handle,CURLOPT_URL,addr.m_endpoint);
80             curl_easy_setopt(m_handle,CURLOPT_CONNECTTIMEOUT,15);
81             curl_easy_setopt(m_handle,CURLOPT_TIMEOUT,30);
82             curl_easy_setopt(m_handle,CURLOPT_HTTPAUTH,0);
83             curl_easy_setopt(m_handle,CURLOPT_USERPWD,nullptr);
84             curl_easy_setopt(m_handle,CURLOPT_SSL_VERIFYHOST,2);
85             curl_easy_setopt(m_handle,CURLOPT_HEADERDATA,this);
86             m_headers=curl_slist_append(m_headers,"Content-Type: text/xml");
87         }
88
89         virtual ~CURLSOAPTransport() {
90             curl_slist_free_all(m_headers);
91             curl_easy_setopt(m_handle, CURLOPT_USERAGENT, nullptr);
92             curl_easy_setopt(m_handle, CURLOPT_ERRORBUFFER, nullptr);
93             curl_easy_setopt(m_handle, CURLOPT_PRIVATE, m_authenticated ? "secure" : nullptr); // Save off security "state".
94             g_CURLPool->put(m_sender.c_str(), m_peerName.c_str(), m_endpoint.c_str(), m_handle);
95         }
96
97         bool isConfidential() const {
98             return m_endpoint.find("https")==0;
99         }
100
101         bool setConnectTimeout(long timeout) {
102             return (curl_easy_setopt(m_handle,CURLOPT_CONNECTTIMEOUT,timeout)==CURLE_OK);
103         }
104
105         bool setTimeout(long timeout) {
106             return (curl_easy_setopt(m_handle,CURLOPT_TIMEOUT,timeout)==CURLE_OK);
107         }
108
109         bool setAuth(transport_auth_t authType, const char* username=nullptr, const char* password=nullptr);
110
111         bool setVerifyHost(bool verify) {
112             return (curl_easy_setopt(m_handle,CURLOPT_SSL_VERIFYHOST,verify ? 2 : 0)==CURLE_OK);
113         }
114
115 #ifndef XMLTOOLING_NO_XMLSEC
116         bool setCredential(const Credential* cred=nullptr) {
117             const OpenSSLCredential* down = dynamic_cast<const OpenSSLCredential*>(cred);
118             if (!down) {
119                 m_cred = nullptr;
120                 return (cred==nullptr);
121             }
122             m_cred = down;
123             return true;
124         }
125
126         bool setTrustEngine(
127             const X509TrustEngine* trustEngine=nullptr,
128             const CredentialResolver* peerResolver=nullptr,
129             CredentialCriteria* criteria=nullptr,
130             bool mandatory=true
131             ) {
132             const OpenSSLTrustEngine* down = dynamic_cast<const OpenSSLTrustEngine*>(trustEngine);
133             if (!down) {
134                 m_trustEngine = nullptr;
135                 m_peerResolver = nullptr;
136                 m_criteria = nullptr;
137                 return (trustEngine==nullptr);
138             }
139             m_trustEngine = down;
140             m_peerResolver = peerResolver;
141             m_criteria = criteria;
142             m_mandatory = mandatory;
143             return true;
144         }
145
146 #endif
147
148         bool useChunkedEncoding(bool chunked=true) {
149             m_chunked = chunked;
150             return true;
151         }
152
153         bool followRedirects(bool follow, unsigned int maxRedirs) {
154             return (
155                 curl_easy_setopt(m_handle, CURLOPT_FOLLOWLOCATION, (follow ? 1 : 0)) == CURLE_OK &&
156                 curl_easy_setopt(m_handle, CURLOPT_MAXREDIRS, (follow ? maxRedirs : 0)) == CURLE_OK
157                 );
158         }
159
160         bool setCacheTag(string* cacheTag) {
161             m_cacheTag = cacheTag;
162             return true;
163         }
164
165         bool setProviderOption(const char* provider, const char* option, const char* value);
166
167         void send(istream& in) {
168             send(&in);
169         }
170
171         void send(istream* in=nullptr);
172
173         istream& receive() {
174             return m_stream;
175         }
176
177         bool isAuthenticated() const {
178             return m_authenticated;
179         }
180
181         void setAuthenticated(bool auth) {
182             m_authenticated = auth;
183         }
184
185         string getContentType() const;
186         long getStatusCode() const;
187
188         bool setRequestHeader(const char* name, const char* val) {
189             string temp(name);
190             temp=temp + ": " + val;
191             m_headers=curl_slist_append(m_headers,temp.c_str());
192             return true;
193         }
194
195         const vector<string>& getResponseHeader(const char* val) const;
196
197         bool setSSLCallback(ssl_ctx_callback_fn fn, void* userptr=nullptr) {
198             m_ssl_callback=fn;
199             m_ssl_userptr=userptr;
200             return true;
201         }
202
203     private:
204         // per-call state
205         string m_sender,m_peerName,m_endpoint,m_simplecreds;
206         CURL* m_handle;
207         stringstream m_stream;
208         struct curl_slist* m_headers;
209                 string m_useragent;
210         map<string,vector<string> > m_response_headers;
211         vector<string> m_saved_options;
212 #ifndef XMLTOOLING_NO_XMLSEC
213         const OpenSSLCredential* m_cred;
214         const OpenSSLTrustEngine* m_trustEngine;
215         const CredentialResolver* m_peerResolver;
216         CredentialCriteria* m_criteria;
217         bool m_mandatory;
218 #endif
219         int m_openssl_ops;
220         ssl_ctx_callback_fn m_ssl_callback;
221         void* m_ssl_userptr;
222         bool m_chunked;
223         bool m_authenticated;
224         string* m_cacheTag;
225
226         friend size_t XMLTOOL_DLLLOCAL curl_header_hook(void* ptr, size_t size, size_t nmemb, void* stream);
227         friend CURLcode XMLTOOL_DLLLOCAL xml_ssl_ctx_callback(CURL* curl, SSL_CTX* ssl_ctx, void* userptr);
228         friend int XMLTOOL_DLLLOCAL verify_callback(X509_STORE_CTX* x509_ctx, void* arg);
229     };
230
231     // libcurl callback functions
232     size_t XMLTOOL_DLLLOCAL curl_header_hook(void* ptr, size_t size, size_t nmemb, void* stream);
233     size_t XMLTOOL_DLLLOCAL curl_write_hook(void* ptr, size_t size, size_t nmemb, void* stream);
234     size_t XMLTOOL_DLLLOCAL curl_read_hook( void *ptr, size_t size, size_t nmemb, void *stream);
235     int XMLTOOL_DLLLOCAL curl_debug_hook(CURL* handle, curl_infotype type, char* data, size_t len, void* ptr);
236     CURLcode XMLTOOL_DLLLOCAL xml_ssl_ctx_callback(CURL* curl, SSL_CTX* ssl_ctx, void* userptr);
237 #ifndef XMLTOOLING_NO_XMLSEC
238     int XMLTOOL_DLLLOCAL verify_callback(X509_STORE_CTX* x509_ctx, void* arg);
239 #endif
240
241     SOAPTransport* CURLSOAPTransportFactory(const SOAPTransport::Address& addr)
242     {
243         return new CURLSOAPTransport(addr);
244     }
245 };
246
247 void xmltooling::registerSOAPTransports()
248 {
249     XMLToolingConfig& conf=XMLToolingConfig::getConfig();
250     conf.SOAPTransportManager.registerFactory("http", CURLSOAPTransportFactory);
251     conf.SOAPTransportManager.registerFactory("https", CURLSOAPTransportFactory);
252 }
253
254 void xmltooling::initSOAPTransports()
255 {
256     g_CURLPool=new CURLPool();
257 }
258
259 void xmltooling::termSOAPTransports()
260 {
261     delete g_CURLPool;
262     g_CURLPool = nullptr;
263 }
264
265 OpenSSLSOAPTransport::OpenSSLSOAPTransport()
266 {
267 }
268
269 OpenSSLSOAPTransport::~OpenSSLSOAPTransport()
270 {
271 }
272
273 CURLPool::~CURLPool()
274 {
275     for (poolmap_t::iterator i=m_bindingMap.begin(); i!=m_bindingMap.end(); i++) {
276         for (vector<CURL*>::iterator j=i->second.begin(); j!=i->second.end(); j++)
277             curl_easy_cleanup(*j);
278     }
279     delete m_lock;
280 }
281
282 CURL* CURLPool::get(const SOAPTransport::Address& addr)
283 {
284 #ifdef _DEBUG
285     xmltooling::NDC("get");
286 #endif
287     m_log.debug("getting connection handle to %s", addr.m_endpoint);
288     string key(addr.m_endpoint);
289     if (addr.m_from)
290         key = key + '|' + addr.m_from;
291     if (addr.m_to)
292         key = key + '|' + addr.m_to;
293     m_lock->lock();
294     poolmap_t::iterator i=m_bindingMap.find(key);
295
296     if (i!=m_bindingMap.end()) {
297         // Move this pool to the front of the list.
298         m_pools.remove(&(i->second));
299         m_pools.push_front(&(i->second));
300
301         // If a free connection exists, return it.
302         if (!(i->second.empty())) {
303             CURL* handle=i->second.back();
304             i->second.pop_back();
305             m_size--;
306             m_lock->unlock();
307             m_log.debug("returning existing connection handle from pool");
308             return handle;
309         }
310     }
311
312     m_lock->unlock();
313     m_log.debug("nothing free in pool, returning new connection handle");
314
315     // Create a new connection and set non-varying options.
316     CURL* handle=curl_easy_init();
317     if (!handle)
318         return nullptr;
319     curl_easy_setopt(handle,CURLOPT_NOPROGRESS,1);
320     curl_easy_setopt(handle,CURLOPT_NOSIGNAL,1);
321     curl_easy_setopt(handle,CURLOPT_FAILONERROR,1);
322     curl_easy_setopt(handle,CURLOPT_SSL_CIPHER_LIST,"ALL:!aNULL:!LOW:!EXPORT:!SSLv2");
323     // Verification of the peer is via TrustEngine only.
324     curl_easy_setopt(handle,CURLOPT_SSL_VERIFYPEER,0);
325     curl_easy_setopt(handle,CURLOPT_CAINFO,nullptr);
326     curl_easy_setopt(handle,CURLOPT_HEADERFUNCTION,&curl_header_hook);
327     curl_easy_setopt(handle,CURLOPT_WRITEFUNCTION,&curl_write_hook);
328     curl_easy_setopt(handle,CURLOPT_DEBUGFUNCTION,&curl_debug_hook);
329     return handle;
330 }
331
332 void CURLPool::put(const char* from, const char* to, const char* endpoint, CURL* handle)
333 {
334     string key(endpoint);
335     if (from)
336         key = key + '|' + from;
337     if (to)
338         key = key + '|' + to;
339     m_lock->lock();
340     poolmap_t::iterator i=m_bindingMap.find(key);
341     if (i==m_bindingMap.end())
342         m_pools.push_front(&(m_bindingMap.insert(poolmap_t::value_type(key,vector<CURL*>(1,handle))).first->second));
343     else
344         i->second.push_back(handle);
345
346     CURL* killit=nullptr;
347     if (++m_size > 256) {
348         // Kick a handle out from the back of the bus.
349         while (true) {
350             vector<CURL*>* corpse=m_pools.back();
351             if (!corpse->empty()) {
352                 killit=corpse->back();
353                 corpse->pop_back();
354                 m_size--;
355                 break;
356             }
357
358             // Move an empty pool up to the front so we don't keep hitting it.
359             m_pools.pop_back();
360             m_pools.push_front(corpse);
361         }
362     }
363     m_lock->unlock();
364     if (killit) {
365         curl_easy_cleanup(killit);
366 #ifdef _DEBUG
367         xmltooling::NDC("put");
368 #endif
369         m_log.info("conn_pool_max limit reached, dropping an old connection");
370     }
371 }
372
373 bool CURLSOAPTransport::setAuth(transport_auth_t authType, const char* username, const char* password)
374 {
375     if (authType==transport_auth_none) {
376         if (curl_easy_setopt(m_handle,CURLOPT_HTTPAUTH,0)!=CURLE_OK)
377             return false;
378         return (curl_easy_setopt(m_handle,CURLOPT_USERPWD,nullptr)==CURLE_OK);
379     }
380     long flag=0;
381     switch (authType) {
382         case transport_auth_basic:    flag = CURLAUTH_BASIC; break;
383         case transport_auth_digest:   flag = CURLAUTH_DIGEST; break;
384         case transport_auth_ntlm:     flag = CURLAUTH_NTLM; break;
385         case transport_auth_gss:      flag = CURLAUTH_GSSNEGOTIATE; break;
386         default:            return false;
387     }
388     if (curl_easy_setopt(m_handle,CURLOPT_HTTPAUTH,flag)!=CURLE_OK)
389         return false;
390     m_simplecreds = string(username ? username : "") + ':' + (password ? password : "");
391     return (curl_easy_setopt(m_handle,CURLOPT_USERPWD,m_simplecreds.c_str())==CURLE_OK);
392 }
393
394 bool CURLSOAPTransport::setProviderOption(const char* provider, const char* option, const char* value)
395 {
396     if (!provider || !option || !value) {
397         return false;
398     }
399     else if (!strcmp(provider, "OpenSSL")) {
400         if (!strcmp(option, "SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION") && (*value=='1' || *value=='t')) {
401             // If the new option to enable buggy rengotiation is available, set it.
402             // Otherwise, signal false if this is newer than 0.9.8k, because that
403             // means it's 0.9.8l, which blocks renegotiation, and therefore will
404             // not honor this request. Older versions are buggy, so behave as though
405             // the flag was set anyway, so we signal true.
406 #if defined(SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION)
407             m_openssl_ops |= SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION;
408             return true;
409 #elif (OPENSSL_VERSION_NUMBER > 0x009080bfL)
410             return false;
411 #else
412             return true;
413 #endif
414         }
415         return false;
416     }
417     else if (strcmp(provider, "CURL")) {
418         return false;
419     }
420
421     // For libcurl, the option is an enum and the value type depends on the option.
422     CURLoption opt = static_cast<CURLoption>(strtol(option, nullptr, 10));
423     if (opt < CURLOPTTYPE_OBJECTPOINT)
424         return (curl_easy_setopt(m_handle, opt, strtol(value, nullptr, 10)) == CURLE_OK);
425 #ifdef CURLOPTTYPE_OFF_T
426     else if (opt < CURLOPTTYPE_OFF_T) {
427         if (value)
428             m_saved_options.push_back(value);
429         return (curl_easy_setopt(m_handle, opt, value ? m_saved_options.back().c_str() : nullptr) == CURLE_OK);
430     }
431 # ifdef HAVE_CURL_OFF_T
432     else if (sizeof(curl_off_t) == sizeof(long))
433         return (curl_easy_setopt(m_handle, opt, strtol(value, nullptr, 10)) == CURLE_OK);
434 # else
435     else if (sizeof(off_t) == sizeof(long))
436         return (curl_easy_setopt(m_handle, opt, strtol(value, nullptr, 10)) == CURLE_OK);
437 # endif
438     return false;
439 #else
440     else {
441         if (value)
442             m_saved_options.push_back(value);
443         return (curl_easy_setopt(m_handle, opt, value ? m_saved_options.back().c_str() : nullptr) == CURLE_OK);
444     }
445 #endif
446 }
447
448 const vector<string>& CURLSOAPTransport::getResponseHeader(const char* name) const
449 {
450     static vector<string> emptyVector;
451
452     map<string,vector<string> >::const_iterator i=m_response_headers.find(name);
453     if (i!=m_response_headers.end())
454         return i->second;
455
456     for (map<string,vector<string> >::const_iterator j=m_response_headers.begin(); j!=m_response_headers.end(); j++) {
457 #ifdef HAVE_STRCASECMP
458         if (!strcasecmp(j->first.c_str(), name))
459 #else
460         if (!stricmp(j->first.c_str(), name))
461 #endif
462             return j->second;
463     }
464
465     return emptyVector;
466 }
467
468 string CURLSOAPTransport::getContentType() const
469 {
470     char* content_type=nullptr;
471     curl_easy_getinfo(m_handle,CURLINFO_CONTENT_TYPE,&content_type);
472     return content_type ? content_type : "";
473 }
474
475 long CURLSOAPTransport::getStatusCode() const
476 {
477     long code=200;
478     if (curl_easy_getinfo(m_handle, CURLINFO_RESPONSE_CODE, &code) != CURLE_OK)
479         code = 200;
480     return code;
481 }
482
483 void CURLSOAPTransport::send(istream* in)
484 {
485 #ifdef _DEBUG
486     xmltooling::NDC ndc("send");
487 #endif
488     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".SOAPTransport.CURL");
489     Category& log_curl=Category::getInstance(XMLTOOLING_LOGCAT".libcurl");
490
491     // For this implementation, it's sufficient to check for https as a sign of transport security.
492     if (m_mandatory && !isConfidential())
493         throw IOException("Blocking unprotected HTTP request, transport authentication by server required.");
494
495     string msg;
496
497     // By this time, the handle has been prepared with the URL to use and the
498     // caller should have executed any set functions to manipulate it.
499
500     // Setup standard per-call curl properties.
501     curl_easy_setopt(m_handle,CURLOPT_DEBUGDATA,&log_curl);
502     curl_easy_setopt(m_handle,CURLOPT_FILE,&m_stream);
503     if (m_chunked && in) {
504         curl_easy_setopt(m_handle,CURLOPT_POST,1);
505         m_headers=curl_slist_append(m_headers,"Transfer-Encoding: chunked");
506         curl_easy_setopt(m_handle,CURLOPT_READFUNCTION,&curl_read_hook);
507         curl_easy_setopt(m_handle,CURLOPT_READDATA,in);
508     }
509     else if (in) {
510         char buf[1024];
511         while (*in) {
512             in->read(buf,1024);
513             msg.append(buf,in->gcount());
514         }
515         curl_easy_setopt(m_handle,CURLOPT_POST,1);
516         curl_easy_setopt(m_handle,CURLOPT_READFUNCTION,nullptr);
517         curl_easy_setopt(m_handle,CURLOPT_POSTFIELDS,msg.c_str());
518         curl_easy_setopt(m_handle,CURLOPT_POSTFIELDSIZE,msg.length());
519     }
520     else {
521         curl_easy_setopt(m_handle,CURLOPT_HTTPGET,1);
522         curl_easy_setopt(m_handle,CURLOPT_FOLLOWLOCATION,1);
523         curl_easy_setopt(m_handle,CURLOPT_MAXREDIRS,6);
524     }
525
526     char curl_errorbuf[CURL_ERROR_SIZE];
527     curl_errorbuf[0]=0;
528     curl_easy_setopt(m_handle,CURLOPT_ERRORBUFFER,curl_errorbuf);
529     if (log_curl.isDebugEnabled())
530         curl_easy_setopt(m_handle,CURLOPT_VERBOSE,1);
531
532     // Check for cache tag.
533     if (m_cacheTag && !m_cacheTag->empty()) {
534         string hdr("If-None-Match: ");
535         hdr += *m_cacheTag;
536         m_headers = curl_slist_append(m_headers, hdr.c_str());
537     }
538
539     m_useragent = XMLToolingConfig::getConfig().user_agent;
540     if (!m_useragent.empty()) {
541         curl_version_info_data* curlver = curl_version_info(CURLVERSION_NOW);
542         m_useragent += " libcurl/";
543         if (curlver)
544             m_useragent = m_useragent + curlver->version + ' ' + curlver->ssl_version;
545         else
546             m_useragent = m_useragent + LIBCURL_VERSION + ' ' + OPENSSL_VERSION_TEXT;
547         curl_easy_setopt(m_handle, CURLOPT_USERAGENT, m_useragent.c_str());
548     }
549
550     // Set request headers.
551     curl_easy_setopt(m_handle,CURLOPT_HTTPHEADER,m_headers);
552
553 #ifndef XMLTOOLING_NO_XMLSEC
554     if (m_ssl_callback || m_cred || m_trustEngine) {
555 #else
556     if (m_ssl_callback) {
557 #endif
558         curl_easy_setopt(m_handle,CURLOPT_SSL_CTX_FUNCTION,xml_ssl_ctx_callback);
559         curl_easy_setopt(m_handle,CURLOPT_SSL_CTX_DATA,this);
560
561         // Restore security "state". Necessary because the callback only runs
562         // when handshakes occur. Even new TCP connections won't execute it.
563         char* priv=nullptr;
564         curl_easy_getinfo(m_handle,CURLINFO_PRIVATE,&priv);
565         if (priv)
566             m_authenticated=true;
567     }
568     else {
569         curl_easy_setopt(m_handle,CURLOPT_SSL_CTX_FUNCTION,nullptr);
570         curl_easy_setopt(m_handle,CURLOPT_SSL_CTX_DATA,nullptr);
571     }
572
573     // Make the call.
574     log.debug("sending SOAP message to %s", m_endpoint.c_str());
575     if (curl_easy_perform(m_handle) != CURLE_OK) {
576         throw IOException(
577             string("CURLSOAPTransport failed while contacting SOAP endpoint (") + m_endpoint + "): " +
578                 (curl_errorbuf[0] ? curl_errorbuf : "no further information available"));
579     }
580
581     // Check for outgoing cache tag.
582     if (m_cacheTag) {
583         const vector<string>& tags = getResponseHeader("ETag");
584         if (!tags.empty())
585             *m_cacheTag = tags.front();
586     }
587 }
588
589 // callback to buffer headers from server
590 size_t xmltooling::curl_header_hook(void* ptr, size_t size, size_t nmemb, void* stream)
591 {
592     // only handle single-byte data
593     if (size!=1)
594         return 0;
595     CURLSOAPTransport* ctx = reinterpret_cast<CURLSOAPTransport*>(stream);
596     char* buf = (char*)malloc(nmemb + 1);
597     if (buf) {
598         memset(buf,0,nmemb + 1);
599         memcpy(buf,ptr,nmemb);
600         char* sep=(char*)strchr(buf,':');
601         if (sep) {
602             *(sep++)=0;
603             while (*sep==' ')
604                 *(sep++)=0;
605             char* white=buf+nmemb-1;
606             while (isspace(*white))
607                 *(white--)=0;
608             ctx->m_response_headers[buf].push_back(sep);
609         }
610         free(buf);
611         return nmemb;
612     }
613     return 0;
614 }
615
616 // callback to send data to server
617 size_t xmltooling::curl_read_hook(void* ptr, size_t size, size_t nmemb, void* stream)
618 {
619     // stream is actually an istream pointer
620     istream* buf=reinterpret_cast<istream*>(stream);
621     buf->read(reinterpret_cast<char*>(ptr),size*nmemb);
622     return buf->gcount();
623 }
624
625 // callback to buffer data from server
626 size_t xmltooling::curl_write_hook(void* ptr, size_t size, size_t nmemb, void* stream)
627 {
628     size_t len = size*nmemb;
629     reinterpret_cast<stringstream*>(stream)->write(reinterpret_cast<const char*>(ptr),len);
630     return len;
631 }
632
633 // callback for curl debug data
634 int xmltooling::curl_debug_hook(CURL* handle, curl_infotype type, char* data, size_t len, void* ptr)
635 {
636     // *ptr is actually a logging object
637     if (!ptr) return 0;
638     CategoryStream log=reinterpret_cast<Category*>(ptr)->debugStream();
639     for (unsigned char* ch=(unsigned char*)data; len && (isprint(*ch) || isspace(*ch)); len--)
640         log << *ch++;
641     return 0;
642 }
643
644 #ifndef XMLTOOLING_NO_XMLSEC
645 int xmltooling::verify_callback(X509_STORE_CTX* x509_ctx, void* arg)
646 {
647     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".SOAPTransport.CURL");
648     log.debug("invoking custom X.509 verify callback");
649 #if (OPENSSL_VERSION_NUMBER >= 0x00907000L)
650     CURLSOAPTransport* ctx = reinterpret_cast<CURLSOAPTransport*>(arg);
651 #else
652     // Yes, this sucks. I'd use TLS, but there's no really obvious spot to put the thread key
653     // and global variables suck too. We can't access the X509_STORE_CTX depth directly because
654     // OpenSSL only copies it into the context if it's >=0, and the unsigned pointer may be
655     // negative in the SSL structure's int member.
656     CURLSOAPTransport* ctx = reinterpret_cast<CURLSOAPTransport*>(
657         SSL_get_verify_depth(
658             reinterpret_cast<SSL*>(X509_STORE_CTX_get_ex_data(x509_ctx,SSL_get_ex_data_X509_STORE_CTX_idx()))
659             )
660         );
661 #endif
662
663     bool success=false;
664     if (ctx->m_criteria) {
665         ctx->m_criteria->setUsage(Credential::TLS_CREDENTIAL);
666         // Bypass name check (handled for us by curl).
667         ctx->m_criteria->setPeerName(nullptr);
668         success = ctx->m_trustEngine->validate(x509_ctx->cert,x509_ctx->untrusted,*(ctx->m_peerResolver),ctx->m_criteria);
669     }
670     else {
671         // Bypass name check (handled for us by curl).
672         CredentialCriteria cc;
673         cc.setUsage(Credential::TLS_CREDENTIAL);
674         success = ctx->m_trustEngine->validate(x509_ctx->cert,x509_ctx->untrusted,*(ctx->m_peerResolver),&cc);
675     }
676
677     if (!success) {
678         log.error("supplied TrustEngine failed to validate SSL/TLS server certificate");
679         x509_ctx->error=X509_V_ERR_APPLICATION_VERIFICATION;     // generic error, check log for plugin specifics
680         ctx->setAuthenticated(false);
681         return ctx->m_mandatory ? 0 : 1;
682     }
683
684     // Signal success. Hopefully it doesn't matter what's actually in the structure now.
685     ctx->setAuthenticated(true);
686     return 1;
687 }
688 #endif
689
690 // callback to invoke a caller-defined SSL callback
691 CURLcode xmltooling::xml_ssl_ctx_callback(CURL* curl, SSL_CTX* ssl_ctx, void* userptr)
692 {
693     CURLSOAPTransport* conf = reinterpret_cast<CURLSOAPTransport*>(userptr);
694
695     // Default flags manually disable SSLv2 so we're not dependent on libcurl to do it.
696     // Also disable the ticket option where implemented, since this breaks a variety
697     // of servers. Newer libcurl also does this for us.
698 #ifdef SSL_OP_NO_TICKET
699     SSL_CTX_set_options(ssl_ctx, conf->m_openssl_ops|SSL_OP_NO_TICKET);
700 #else
701     SSL_CTX_set_options(ssl_ctx, conf->m_openssl_ops);
702 #endif
703
704 #ifndef XMLTOOLING_NO_XMLSEC
705     if (conf->m_cred)
706         conf->m_cred->attach(ssl_ctx);
707
708     if (conf->m_trustEngine) {
709         SSL_CTX_set_verify(ssl_ctx,SSL_VERIFY_PEER,nullptr);
710 #if (OPENSSL_VERSION_NUMBER >= 0x00907000L)
711         // With 0.9.7, we can pass a callback argument directly.
712         SSL_CTX_set_cert_verify_callback(ssl_ctx,verify_callback,userptr);
713 #else
714         // With 0.9.6, there's no argument, so we're going to use a really embarrassing hack and
715         // stuff the argument in the depth property where it will get copied to the context object
716         // that's handed to the callback.
717         SSL_CTX_set_cert_verify_callback(ssl_ctx,reinterpret_cast<int (*)()>(verify_callback),nullptr);
718         SSL_CTX_set_verify_depth(ssl_ctx,reinterpret_cast<int>(userptr));
719 #endif
720     }
721 #endif
722
723     if (conf->m_ssl_callback && !conf->m_ssl_callback(conf, ssl_ctx, conf->m_ssl_userptr))
724         return CURLE_SSL_CERTPROBLEM;
725
726     return CURLE_OK;
727 }