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