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