XRootD
Loading...
Searching...
No Matches
XrdHttpTpcTPC.cc
Go to the documentation of this file.
4#include "XrdOuc/XrdOucEnv.hh"
8#include "XrdSys/XrdSysFD.hh"
9#include "XrdVersion.hh"
10
17
18#include <curl/curl.h>
19
20#include <dlfcn.h>
21#include <fcntl.h>
22
23#include <algorithm>
24#include <memory>
25#include <sstream>
26#include <stdexcept>
27#include <thread>
28
29#include "XrdHttpTpcState.hh"
30#include "XrdHttpTpcStream.hh"
31#include "XrdHttpTpcTPC.hh"
32#include <fstream>
33
34using namespace TPC;
35
36XrdXrootdTpcMon* TPCHandler::TPCLogRecord::tpcMonitor = 0;
37
38uint64_t TPCHandler::m_monid{0};
39int TPCHandler::m_marker_period = 5;
40size_t TPCHandler::m_block_size = 16*1024*1024;
41size_t TPCHandler::m_small_block_size = 1*1024*1024;
42XrdSysMutex TPCHandler::m_monid_mutex;
43bool TPCHandler::allowMissingCRL = false;
44
46
47/******************************************************************************/
48/* T P C H a n d l e r : : T P C L o g R e c o r d D e s t r u c t o r */
49/******************************************************************************/
50
51TPCHandler::TPCLogRecord::~TPCLogRecord()
52{
53// Record monitoring data is enabled
54//
55 if (tpcMonitor)
57
58 monInfo.clID = clID.c_str();
59 monInfo.begT = begT;
60 gettimeofday(&monInfo.endT, 0);
61
62 if (mTpcType == TpcType::Pull)
63 {monInfo.dstURL = local.c_str();
64 monInfo.srcURL = remote.c_str();
65 } else {
66 monInfo.dstURL = remote.c_str();
67 monInfo.srcURL = local.c_str();
69 }
70
71 if (!status) monInfo.endRC = 0;
72 else if (tpc_status > 0) monInfo.endRC = tpc_status;
73 else monInfo.endRC = 1;
74 monInfo.strm = static_cast<unsigned char>(streams);
75 monInfo.fSize = (bytes_transferred < 0 ? 0 : bytes_transferred);
76 if (!isIPv6) monInfo.opts |= XrdXrootdTpcMon::TpcInfo::isIPv4;
77
78 tpcMonitor->Report(monInfo);
79 }
80}
81
82/******************************************************************************/
83/* C u r l D e l e t e r : : o p e r a t o r ( ) */
84/******************************************************************************/
85
87{
88 if (curl) curl_easy_cleanup(curl);
89}
90
91/******************************************************************************/
92/* s o c k o p t _ s e t c l o e x e c _ c a l l b a c k */
93/******************************************************************************/
94
103int TPCHandler::sockopt_callback(void *clientp, curl_socket_t curlfd, curlsocktype purpose) {
104 TPCLogRecord * rec = (TPCLogRecord *)clientp;
105 if (purpose == CURLSOCKTYPE_IPCXN && rec && rec->pmarkManager.isEnabled()) {
106 // We will not reach this callback if the corresponding socket could not have been connected
107 // the socket is already connected only if the packet marking is enabled
108 return CURL_SOCKOPT_ALREADY_CONNECTED;
109 }
110 return CURL_SOCKOPT_OK;
111}
112
113/******************************************************************************/
114/* o p e n s o c k e t _ c a l l b a c k */
115/******************************************************************************/
116
117
122int TPCHandler::opensocket_callback(void *clientp,
123 curlsocktype purpose,
124 struct curl_sockaddr *aInfo)
125{
126 /* CURLSOCKTYPE_IPCXN (for IP based connections) is the only type currently known by curl,
127 * so let's make sure to reject other types if they appear in the furure */
128 if (purpose != CURLSOCKTYPE_IPCXN)
129 return CURL_SOCKET_BAD;
130
131 if (!aInfo)
132 return CURL_SOCKET_BAD;
133
134 // Create the socket (note that O_CLOEXEC flag will be set)
135 int fd = XrdSysFD_Socket(aInfo->family, aInfo->socktype, aInfo->protocol);
136
137 if (fd < 0) {
138 return CURL_SOCKET_BAD;
139 }
140
141 if (!clientp)
142 return fd;
143
144 XrdNetAddr thePeer(&(aInfo->addr));
145 TPCLogRecord *rec = static_cast<TPCLogRecord*>(clientp);
146
147 /* Reject attempts to connect to local/private addresses unless allowed by configuration */
148 if ((!rec->allow_private && thePeer.isPrivate()) || (!rec->allow_local && thePeer.isLocal())) {
149 rec->tpc_status = 403; // Forbidden
150 rec->m_log->Emsg(rec->log_prefix.c_str(),
151 "Connection to local/private address is forbidden");
152 close(fd);
153 return CURL_SOCKET_BAD;
154 }
155
156 rec->isIPv6 = (thePeer.isIPType(XrdNetAddrInfo::IPv6) && !thePeer.isMapped());
157
158 std::stringstream connectErrMsg;
159 if(!rec->pmarkManager.connect(fd, &(aInfo->addr), aInfo->addrlen, CONNECT_TIMEOUT, connectErrMsg)) {
160 // at this point fd has already been closed
161 rec->m_log->Emsg(rec->log_prefix.c_str(), "Unable to connect socket: ", connectErrMsg.str().c_str());
162 return CURL_SOCKET_BAD;
163 }
164
165 return fd;
166}
167
168int TPCHandler::closesocket_callback(void *clientp, curl_socket_t fd) {
169 TPCLogRecord * rec = (TPCLogRecord *)clientp;
170
171 // Destroy the PMark handle associated to the file descriptor before closing it.
172 // Otherwise, we would lose the socket usage information if the socket is closed before
173 // the PMark handle is closed.
174 rec->pmarkManager.endPmark(fd);
175
176 return close(fd);
177}
178
179/******************************************************************************/
180/* s s l _ c t x _ c a l l b a c k */
181/******************************************************************************/
182
189int TPCHandler::ssl_ctx_callback(CURL *curl, void *ssl_ctx, void *clientp) {
190 TPCLogRecord * rec = (TPCLogRecord *)clientp;
191 SSL_CTX* ctx = static_cast<SSL_CTX*>(ssl_ctx);
192
193 if (rec && rec->ca_store) {
194 // Bumps the store's reference count instead of re-parsing the CA and CRL
195 // bundles for this connection. libcurl runs this callback after it has
196 // applied its own TLS options, so this replaces whatever store it built.
197 SSL_CTX_set1_cert_store(ctx, rec->ca_store.get());
198 }
199 if (allowMissingCRL) {
200 // verify_callback only excuses X509_V_ERR_UNABLE_TO_GET_CRL, i.e. a CA in
201 // the chain for which no CRL could be found. Every other verification rule
202 // still applies, including revocation itself whenever a CRL is present.
203 SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback);
204 }
205 return CURLE_OK;
206}
207
208int TPCHandler::verify_callback(int preverify_ok, X509_STORE_CTX* ctx) {
209 if (preverify_ok == 1) return 1;
210
211 int err = X509_STORE_CTX_get_error(ctx);
212
213 if (err == X509_V_ERR_UNABLE_TO_GET_CRL) {
214 X509_STORE_CTX_set_error(ctx, X509_V_OK);
215 return 1;
216 }
217
218 return 0;
219}
220
221/******************************************************************************/
222/* p r e p a r e U R L */
223/******************************************************************************/
224
225// See XrdHttpTpcUtils::prepareOpenURL() documentation
226std::string TPCHandler::prepareURL(XrdHttpExtReq &req) {
227 XrdHttpTpcUtils::PrepareOpenURLParams parms {req.resource, req.headers, hdr2cgimap,req.mReprDigest};
229}
230
231bool TPCHandler::mismatchReprDigest(const std::map<std::string, std::string> & passiveSrvReprDigest, XrdHttpExtReq &req,
232 TPCLogRecord &rec) {
233 if(passiveSrvReprDigest.size()) {
234 for (const auto & [digestName, digestValue]: passiveSrvReprDigest) {
235 auto clientDigestMatch = req.mReprDigest.find(digestName);
236 if (clientDigestMatch != req.mReprDigest.end()) {
237 // We found a checksum type match between the client-provided one and the source server-provided one
238 if (clientDigestMatch->second != digestValue) {
239 // The checksum value does not match, return an error to the client 412 PRECONDITION_FAILED
240 std::stringstream errMsg;
241 errMsg << "Mismatch between client-provided and remote server checksums:"
242 << " client = (" << clientDigestMatch->first << "=" << clientDigestMatch->second << ")"
243 << " server = (" << digestName << "=" << digestValue << ")";
244 logTransferEvent(LogMask::Error, rec, "REPRDIGEST_VERIFY_FAIL", errMsg.str());
245 rec.status=412;
246 req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(errMsg, rec, CURLcode::CURLE_OK).c_str(), 0);
247 return true;
248 }
249 }
250 }
251 }
252 return false;
253}
254
255/******************************************************************************/
256/* e n c o d e _ x r o o t d _ o p a q u e _ t o _ u r i */
257/******************************************************************************/
258
259// When processing a redirection from the filesystem layer, it is permitted to return
260// some xrootd opaque data. The quoting rules for xrootd opaque data are significantly
261// more permissive than a URI (basically, only '&' and '=' are disallowed while some
262// URI parsers may dislike characters like '"'). This function takes an opaque string
263// (e.g., foo=1&bar=2&baz=") and makes it safe for all URI parsers.
264std::string encode_xrootd_opaque_to_uri(CURL *curl, const std::string &opaque)
265{
266 std::stringstream parser(opaque);
267 std::string sequence;
268 std::stringstream output;
269 bool first = true;
270 while (getline(parser, sequence, '&')) {
271 if (sequence.empty()) {continue;}
272 size_t equal_pos = sequence.find('=');
273 char *val = NULL;
274 if (equal_pos != std::string::npos)
275 val = curl_easy_escape(curl, sequence.c_str() + equal_pos + 1, sequence.size() - equal_pos - 1);
276 // Do not emit parameter if value exists and escaping failed.
277 if (!val && equal_pos != std::string::npos) {continue;}
278
279 if (!first) output << "&";
280 first = false;
281 output << sequence.substr(0, equal_pos);
282 if (val) {
283 output << "=" << val;
284 curl_free(val);
285 }
286 }
287 return output.str();
288}
289
290/******************************************************************************/
291/* T P C H a n d l e r : : C o n f i g u r e C u r l C A */
292/******************************************************************************/
293
294bool
295TPCHandler::ConfigureCurlCA(CURL *curl, TPCLogRecord &rec)
296{
297 // Preferred path: hand libcurl the CA/CRL store that XrdTlsTempCA already
298 // parsed, rather than the bundle filenames. Passing filenames makes libcurl
299 // build a private X509_STORE per connection, which costs tens of MB for a grid
300 // CA directory and is held for the whole transfer; sharing one store makes that
301 // a reference count. See https://github.com/xrootd/xrootd/issues/2873
302 //
303 // Skipped when m_cafile is set, so that the http.cafile precedence established
304 // at the bottom of this function is preserved.
305 if (m_ca_file && m_sslctx_supported && m_cafile.empty()) {
306 rec.ca_store = m_ca_file->CAStore();
307 if (!rec.ca_store) {
308 m_log.Log(Error, "TpcHandler", "No CA store is available; refusing to "
309 "fall back to libcurl's default CA bundle");
310 return false;
311 }
312 // Stop libcurl loading its build-time default bundle, which the callback
313 // below would only discard; the callback supplies the trust anchors.
314 curl_easy_setopt(curl, CURLOPT_CAINFO, static_cast<char *>(nullptr));
315 curl_easy_setopt(curl, CURLOPT_CAPATH, static_cast<char *>(nullptr));
316 curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, ssl_ctx_callback);
317 curl_easy_setopt(curl, CURLOPT_SSL_CTX_DATA, &rec);
318 return true;
319 }
320
321 auto ca_filename = m_ca_file ? m_ca_file->CAFilename() : "";
322 auto crl_filename = m_ca_file ? m_ca_file->CRLFilename() : "";
323 if (!ca_filename.empty() && !crl_filename.empty()) {
324 curl_easy_setopt(curl, CURLOPT_CAINFO, ca_filename.c_str());
325 //Check that the CRL file contains at least one entry before setting this option to curl
326 //Indeed, an empty CRL file will make curl unhappy and therefore will fail
327 //all HTTP TPC transfers (https://github.com/xrootd/xrootd/issues/1543)
328 std::ifstream in(crl_filename, std::ifstream::ate | std::ifstream::binary);
329 if(in.tellg() > 0 && m_ca_file->atLeastOneValidCRLFound()){
330 curl_easy_setopt(curl, CURLOPT_CRLFILE, crl_filename.c_str());
331 if (allowMissingCRL) {
332 // No need to set the callback if there is no need to do it
333 curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, ssl_ctx_callback);
334 }
335 } else {
336 std::ostringstream oss;
337 oss << "No valid CRL file has been found in the file " << crl_filename << ". Disabling CRL checking.";
338 m_log.Log(Warning,"TpcHandler",oss.str().c_str());
339 }
340 }
341 else if (!m_cadir.empty()) {
342 curl_easy_setopt(curl, CURLOPT_CAPATH, m_cadir.c_str());
343 }
344 if (!m_cafile.empty()) {
345 curl_easy_setopt(curl, CURLOPT_CAINFO, m_cafile.c_str());
346 }
347 return true;
348}
349
350void
351TPCHandler::ConfigureCurlLowSpeed(CURL *curl)
352{
353 // Older versions have poor transfer performance when low-speed limits are
354 // enabled; this was corrected in curl commit cacdc27f for version 7.38.0.
355 curl_version_info_data *curl_ver = curl_version_info(CURLVERSION_NOW);
356 if (m_low_speed_limit > 0 && curl_ver && curl_ver->age > 0 &&
357 curl_ver->version_num >= 0x072600) {
358 curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, m_low_speed_time);
359 curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, m_low_speed_limit);
360 }
361}
362
363
364bool TPCHandler::MatchesPath(const char *verb, const char *path) {
365 return !strcmp(verb, "COPY") || !strcmp(verb, "OPTIONS");
366}
367
368/******************************************************************************/
369/* P r e p a r e U R L */
370/******************************************************************************/
371
372static std::string PrepareURL(const std::string &url)
373{
374 const std::string replace_schemes[] = { "davs://", "s3://", "s3s://" };
375
376 for (const auto& s : replace_schemes)
377 if (url.compare(0, s.size(), s) == 0)
378 return "https://" + url.substr(s.size());
379
380 return url;
381}
382
383static bool IsAllowedScheme(const std::string& url)
384{
385 const std::string allowed_schemes[] = { "https://", "http://" };
386
387 for (const auto& s : allowed_schemes)
388 if (url.compare(0, s.size(), s) == 0)
389 return true;
390
391 return false;
392}
393
394/******************************************************************************/
395/* T P C H a n d l e r : : P r o c e s s R e q */
396/******************************************************************************/
397
399 if (req.verb == "OPTIONS") {
400 return ProcessOptionsReq(req);
401 }
402 auto header = XrdOucTUtils::caseInsensitiveFind(req.headers,"credential");
403 if (header != req.headers.end()) {
404 if (header->second != "none") {
405 m_log.Emsg("ProcessReq", "COPY requested an unsupported credential type: ", header->second.c_str());
406 return req.SendSimpleResp(400, NULL, NULL, "COPY requestd an unsupported Credential type", 0);
407 }
408 }
409 header = XrdOucTUtils::caseInsensitiveFind(req.headers,"source");
410 if (header != req.headers.end()) {
411 std::string src = PrepareURL(header->second);
412 if (!IsAllowedScheme(src)) {
413 const char *error_src = "COPY rejected: disallowed scheme in source URL";
414 m_log.Emsg("ProcessReq", error_src, src.c_str());
415 return req.SendSimpleResp(400, NULL, NULL, error_src, 0);
416 }
417 return ProcessPullReq(src, req);
418 }
419 header = XrdOucTUtils::caseInsensitiveFind(req.headers,"destination");
420 if (header != req.headers.end()) {
421 const std::string& dst = header->second;
422 if (!IsAllowedScheme(dst)) {
423 const char *error_dst = "COPY rejected: disallowed scheme in destination URL";
424 m_log.Emsg("ProcessReq", error_dst, dst.c_str());
425 return req.SendSimpleResp(400, NULL, NULL, error_dst, 0);
426 }
427 return ProcessPushReq(header->second, req);
428 }
429 m_log.Emsg("ProcessReq", "COPY verb requested but no source or destination specified.");
430 return req.SendSimpleResp(400, NULL, NULL, "No Source or Destination specified", 0);
431}
432
433/******************************************************************************/
434/* T P C H a n d l e r D e s t r u c t o r */
435/******************************************************************************/
436
438 m_sfs = NULL;
439}
440
441/******************************************************************************/
442/* T P C H a n d l e r C o n s t r u c t o r */
443/******************************************************************************/
444
445TPCHandler::TPCHandler(XrdSysError *log, const char *config, XrdOucEnv *myEnv) :
446 m_allow_local(false),
447 m_allow_private(true),
448 m_desthttps(false),
449 m_fixed_route(false),
450 m_low_speed_limit(10*1024),
451 m_low_speed_time(2*60),
452 m_timeout(60),
453 m_first_timeout(120),
454 m_log(log->logger(), "TPC_"),
455 m_sfs(NULL)
456{
457 if (!Configure(config, myEnv)) {
458 throw std::runtime_error("Failed to configure the HTTP third-party-copy handler.");
459 }
460
461// Extract out the TPC monitoring object (we share it with xrootd).
462//
463 XrdXrootdGStream *gs = (XrdXrootdGStream*)myEnv->GetPtr("Tpc.gStream*");
464 if (gs)
465 TPCLogRecord::tpcMonitor = new XrdXrootdTpcMon("http",log->logger(),*gs);
466}
467
468/******************************************************************************/
469/* T P C H a n d l e r : : P r o c e s s O p t i o n s R e q */
470/******************************************************************************/
471
475int TPCHandler::ProcessOptionsReq(XrdHttpExtReq &req) {
476 return req.SendSimpleResp(200, NULL, (char *) "DAV: 1\r\nDAV: <http://apache.org/dav/propset/fs/1>\r\nAllow: HEAD,GET,PUT,PROPFIND,DELETE,OPTIONS,COPY", NULL, 0);
477}
478
479/******************************************************************************/
480/* T P C H a n d l e r : : G e t A u t h z */
481/******************************************************************************/
482
483std::string TPCHandler::GetAuthz(XrdHttpExtReq &req) {
484 std::string authz;
485 auto authz_header = XrdOucTUtils::caseInsensitiveFind(req.headers,"authorization");
486 if (authz_header != req.headers.end()) {
487 std::stringstream ss;
488 ss << "authz=" << encode_str(authz_header->second);
489 authz += ss.str();
490 }
491 return authz;
492}
493
494/******************************************************************************/
495/* T P C H a n d l e r : : R e d i r e c t T r a n s f e r */
496/******************************************************************************/
497
498int TPCHandler::RedirectTransfer(CURL *curl, const std::string &redirect_resource,
499 XrdHttpExtReq &req, XrdOucErrInfo &error, TPCLogRecord &rec)
500{
501 int port;
502 const char *ptr = error.getErrText(port);
503 if ((ptr == NULL) || (*ptr == '\0') || (port == 0)) {
504 rec.status = 500;
505 std::stringstream ss;
506 ss << "Internal error: redirect without hostname";
507 logTransferEvent(LogMask::Error, rec, "REDIRECT_INTERNAL_ERROR", ss.str());
508 return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
509 }
510
511 // The XrdSfs layer hands back the redirect target in host[?cgi] form; the
512 // port arrived separately via getErrText() above. Default to that target
513 // and let the redirect plugin block below override it if it rewrites it.
514 std::string finalTarget = ptr;
515
516 // When a redirect plugin (XrdXrootdRedirPI) is configured, run the COPY
517 // redirect target through it so the same plugin-driven routing applies
518 // as in the XRootD protocol's fsRedirPI(). The plugin may rewrite host,
519 // port, and CGI; on a fatal plugin error surface a 500 with the plugin's
520 // message rather than emit a redirect we know is wrong. See issue #2767.
521 if (XrdNetAddrInfo *clientAddr = req.GetSecEntity().addrInfo;
522 XrdXrootdRedirHelper::IsActive() && clientAddr) {
523 // Redirect() takes the host[?cgi] target as a single string and splits
524 // it itself; the non-negative port selects its host+port form.
525 int newPort = port;
526 std::string newTarget;
527 std::string errMsg;
528 auto outcome = XrdXrootdRedirHelper::Redirect(ptr, newPort, *clientAddr,
529 newTarget, errMsg);
531 finalTarget = std::move(newTarget);
532 port = newPort;
533 logTransferEvent(LogMask::Info, rec, "REDIRECT_PLUGIN_REWRITE",
534 finalTarget);
535 } else if (outcome == XrdXrootdRedirHelper::Outcome::Error) {
536 rec.status = 500;
537 std::stringstream ess;
538 ess << "Redirect plugin error: " << errMsg;
539 logTransferEvent(LogMask::Error, rec, "REDIRECT_PLUGIN_ERROR",
540 ess.str());
541 return req.SendSimpleResp(rec.status, nullptr, nullptr,
542 generateClientErr(ess, rec).c_str(), 0);
543 }
544 // Outcome::Unchanged: keep the original target.
545 }
546
547 // Split the (possibly plugin-rewritten) host[?cgi] target: the host goes
548 // into the Location authority, the cgi into its query string. splitHostCgi
549 // keeps the leading '?' on cgi; the Location builder below wants the bare
550 // opaque body, so drop that '?' here.
551 std::string host;
552 std::string cgi;
553 splitHostCgi(finalTarget, host, cgi);
554 std::string opaque = cgi.empty() ? std::string() : cgi.substr(1);
555
556 std::stringstream ss;
557 ss << "Location: http" << (m_desthttps ? "s" : "") << "://" << host << ":" << port << "/" << redirect_resource;
558
559 if (!opaque.empty()) {
560 // redirect_resource (sourced from xrd-http-fullresource) may already
561 // carry the client's query string, so pick the separator accordingly
562 // to avoid emitting a malformed URL with two '?'.
563 char sep = (redirect_resource.find('?') == std::string::npos) ? '?' : '&';
564 ss << sep << encode_xrootd_opaque_to_uri(curl, opaque);
565 }
566
567 rec.status = 307;
568 logTransferEvent(LogMask::Info, rec, "REDIRECT", ss.str());
569 return req.SendSimpleResp(rec.status, NULL, const_cast<char *>(ss.str().c_str()),
570 NULL, 0);
571}
572
573/******************************************************************************/
574/* T P C H a n d l e r : : O p e n W a i t S t a l l */
575/******************************************************************************/
576
577int TPCHandler::OpenWaitStall(XrdSfsFile &fh, const std::string &resource,
578 int mode, int openMode, const XrdSecEntity &sec,
579 const std::string &authz)
580{
581 int open_result;
582 while (1) {
583 int orig_ucap = fh.error.getUCap();
584 fh.error.setUCap(orig_ucap | XrdOucEI::uIPv64);
585 std::string opaque;
586 size_t pos = resource.find('?');
587 // Extract the path and opaque info from the resource
588 std::string path = resource.substr(0, pos);
589
590 if (pos != std::string::npos) {
591 opaque = resource.substr(pos + 1);
592 }
593
594 // Append the authz information if there are some
595 if(!authz.empty()) {
596 opaque += (opaque.empty() ? "" : "&");
597 opaque += authz;
598 }
599 open_result = fh.open(path.c_str(), mode, openMode, &sec, opaque.c_str());
600
601 if ((open_result == SFS_STALL) || (open_result == SFS_STARTED)) {
602 int secs_to_stall = fh.error.getErrInfo();
603 if (open_result == SFS_STARTED) {secs_to_stall = secs_to_stall/2 + 5;}
604 std::this_thread::sleep_for (std::chrono::seconds(secs_to_stall));
605 }
606 break;
607 }
608 return open_result;
609}
610
611/******************************************************************************/
612/* T P C H a n d l e r : : D e t e r m i n e X f e r S i z e */
613/******************************************************************************/
614
615
616
620int TPCHandler::PerformHEADRequest(CURL *curl, XrdHttpExtReq &req, State &state,
621 bool &success, TPCLogRecord &rec, bool shouldReturnErrorToClient) {
622 success = false;
623 curl_easy_setopt(curl, CURLOPT_NOBODY, 1);
624 // Set a custom timeout of 60 seconds (= CONNECT_TIMEOUT for convenience) for the HEAD request
625 curl_easy_setopt(curl, CURLOPT_TIMEOUT, CONNECT_TIMEOUT);
626 CURLcode res;
627 res = curl_easy_perform(curl);
628 //Immediately set the CURLOPT_NOBODY flag to 0 as we anyway
629 //don't want the next curl call to do be a HEAD request
630 curl_easy_setopt(curl, CURLOPT_NOBODY, 0);
631 // Reset the CURLOPT_TIMEOUT to no timeout (default)
632 curl_easy_setopt(curl, CURLOPT_TIMEOUT, 0L);
633 curl_easy_setopt(curl, CURLOPT_FAILONERROR, true);
634
635 std::stringstream ss;
636
637 if (state.GetStatusCode() >= 400)
638 res = CURLE_HTTP_RETURNED_ERROR;
639
640 if (res != CURLE_OK) { /* curl failed */
641 ss << curl_easy_strerror(res);
642 switch (res) {
643 case CURLE_HTTP_RETURNED_ERROR: /* remote side may have returned an error */
644 rec.tpc_status = state.GetStatusCode(); /* relay status received from remote side to the client */
645 ss << ": remote host returned '" << rec.tpc_status << " "
646 << httpStatusToString(rec.tpc_status) << "' while fetching file size";
647 break;
648 case CURLE_COULDNT_CONNECT: /* socket callback may have failed */
649 switch (rec.tpc_status) {
650 case 403:
651 ss << ": connection to local/private addresses is forbidden";
652 break;
653 default:
654 ss << ": internal server failure";
655 rec.tpc_status = 500;
656 }
657 break;
658 default:
659 rec.tpc_status = 500;
660 state.SetErrorCode(500);
661 }
662 }
663
664 if (rec.tpc_status >= 400) {
665 logTransferEvent(LogMask::Error, rec, "HEAD_FAIL", ss.str());
666 return shouldReturnErrorToClient ? req.SendSimpleResp(rec.tpc_status, NULL, NULL, generateClientErr(ss, rec, res).c_str(), 0) : -1;
667 }
668
669 success = true;
670 ss << "Successfully determined remote file information for pull request: "
671 << "size=" << state.GetContentLength();
672 if(state.GetReprDigest().size()) {
673 unsigned int cksumIndex = 1;
674 for(const auto & [cksumType,cksumValue]: state.GetReprDigest()) {
675 ss << " chksum" << cksumIndex << "=(" << cksumType << "," << cksumValue << ")";
676 cksumIndex++;
677 }
678 }
679 logTransferEvent(LogMask::Debug, rec, "HEAD_SUCCESS", ss.str());
680 return 0;
681}
682
683int TPCHandler::GetRemoteFileInfoTPCPull(CURL *curl, XrdHttpExtReq &req, uint64_t &contentLength, std::map<std::string,std::string> & reprDigest, bool & success, TPCLogRecord &rec) {
684 State state(curl,req.tpcForwardCreds);
685 //Don't forget to copy the headers of the client's request before doing the HEAD call. Otherwise, if there is a need for authentication,
686 //it will fail
687 state.SetupHeadersForHEAD(req);
688 int result;
689 //In case we cannot get the file HEAD request, we return the error to the client
690 if ((result = PerformHEADRequest(curl, req, state, success, rec)) || !success) {
691 return result;
692 }
693 contentLength = state.GetContentLength();
694 reprDigest = state.GetReprDigest();
695 return result;
696}
697
698/******************************************************************************/
699/* T P C H a n d l e r : : S e n d P e r f M a r k e r */
700/******************************************************************************/
701
702int TPCHandler::SendPerfMarker(XrdHttpExtReq &req, TPCLogRecord &rec, TPC::State &state) {
703 std::stringstream ss;
704 const std::string crlf = "\n";
705 ss << "Perf Marker" << crlf;
706 ss << "Timestamp: " << time(NULL) << crlf;
707 ss << "Stripe Index: 0" << crlf;
708 ss << "Stripe Bytes Transferred: " << state.BytesTransferred() << crlf;
709 ss << "Total Stripe Count: 1" << crlf;
710 // Include the TCP connection associated with this transfer; used by
711 // the TPC client for monitoring purposes.
712 std::string desc = state.GetConnectionDescription();
713 if (!desc.empty())
714 ss << "RemoteConnections: " << desc << crlf;
715 ss << "End" << crlf;
716 rec.bytes_transferred = state.BytesTransferred();
717 logTransferEvent(LogMask::Debug, rec, "PERF_MARKER");
718
719 return req.ChunkResp(ss.str().c_str(), 0);
720}
721
722/******************************************************************************/
723/* T P C H a n d l e r : : S e n d P e r f M a r k e r */
724/******************************************************************************/
725
726int TPCHandler::SendPerfMarker(XrdHttpExtReq &req, TPCLogRecord &rec, std::vector<State*> &state,
727 off_t bytes_transferred)
728{
729 // The 'performance marker' format is largely derived from how GridFTP works
730 // (e.g., the concept of `Stripe` is not quite so relevant here). See:
731 // https://twiki.cern.ch/twiki/bin/view/LCG/HttpTpcTechnical
732 // Example marker:
733 // Perf Marker\n
734 // Timestamp: 1537788010\n
735 // Stripe Index: 0\n
736 // Stripe Bytes Transferred: 238745\n
737 // Total Stripe Count: 1\n
738 // RemoteConnections: tcp:129.93.3.4:1234,tcp:[2600:900:6:1301:268a:7ff:fef6:a590]:2345\n
739 // End\n
740 //
741 std::stringstream ss;
742 const std::string crlf = "\n";
743 ss << "Perf Marker" << crlf;
744 ss << "Timestamp: " << time(NULL) << crlf;
745 ss << "Stripe Index: 0" << crlf;
746 ss << "Stripe Bytes Transferred: " << bytes_transferred << crlf;
747 ss << "Total Stripe Count: 1" << crlf;
748 // Build a list of TCP connections associated with this transfer; used by
749 // the TPC client for monitoring purposes.
750 bool first = true;
751 std::stringstream ss2;
752 for (std::vector<State*>::const_iterator iter = state.begin();
753 iter != state.end(); iter++)
754 {
755 std::string desc = (*iter)->GetConnectionDescription();
756 if (!desc.empty()) {
757 ss2 << (first ? "" : ",") << desc;
758 first = false;
759 }
760 }
761 if (!first)
762 ss << "RemoteConnections: " << ss2.str() << crlf;
763 ss << "End" << crlf;
764 rec.bytes_transferred = bytes_transferred;
765 logTransferEvent(LogMask::Debug, rec, "PERF_MARKER");
766
767 return req.ChunkResp(ss.str().c_str(), 0);
768}
769
770/******************************************************************************/
771/* T P C H a n d l e r : : R u n C u r l W i t h U p d a t e s */
772/******************************************************************************/
773
774int TPCHandler::RunCurlWithUpdates(CURL *curl, XrdHttpExtReq &req, State &state,
775 TPCLogRecord &rec)
776{
777 // Create the multi-handle and add in the current transfer to it.
778 CURLM *multi_handle = curl_multi_init();
779 if (!multi_handle) {
780 rec.status = 500;
781 logTransferEvent(LogMask::Error, rec, "CURL_INIT_FAIL",
782 "Failed to initialize a libcurl multi-handle");
783 std::stringstream ss;
784 ss << "Failed to initialize internal server memory";
785 return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
786 }
787
788 //curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 128*1024);
789
790 CURLMcode mres;
791 mres = curl_multi_add_handle(multi_handle, curl);
792 if (mres) {
793 rec.status = 500;
794 std::stringstream ss;
795 ss << "Failed to add transfer to libcurl multi-handle: HTTP library failure=" << curl_multi_strerror(mres);
796 logTransferEvent(LogMask::Error, rec, "CURL_INIT_FAIL", ss.str());
797 curl_multi_cleanup(multi_handle);
798 return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
799 }
800
801 // Start response to client prior to the first call to curl_multi_perform
802 int retval = req.StartChunkedResp(202, NULL, "Content-Type: text/plain");
803 if (retval) {
804 curl_multi_cleanup(multi_handle);
805 logTransferEvent(LogMask::Error, rec, "RESPONSE_FAIL",
806 "Failed to send the initial response to the TPC client");
807 return retval;
808 } else {
809 logTransferEvent(LogMask::Debug, rec, "RESPONSE_START",
810 "Initial transfer response sent to the TPC client");
811 }
812
813 // Transfer loop: use curl to actually run the transfer, but periodically
814 // interrupt things to send back performance updates to the client.
815 int running_handles = 1;
816 time_t last_marker = 0;
817 // Track how long it's been since the last time we recorded more bytes being transferred.
818 off_t last_advance_bytes = 0;
819 time_t last_advance_time = time(NULL);
820 time_t transfer_start = last_advance_time;
821 CURLcode res = static_cast<CURLcode>(-1);
822 do {
823 time_t now = time(NULL);
824 time_t next_marker = last_marker + m_marker_period;
825 if (now >= next_marker) {
826 off_t bytes_xfer = state.BytesTransferred();
827 if (bytes_xfer > last_advance_bytes) {
828 last_advance_bytes = bytes_xfer;
829 last_advance_time = now;
830 }
831 if (SendPerfMarker(req, rec, state)) {
832 curl_multi_remove_handle(multi_handle, curl);
833 curl_multi_cleanup(multi_handle);
834 logTransferEvent(LogMask::Error, rec, "PERFMARKER_FAIL",
835 "Failed to send a perf marker to the TPC client");
836 return -1;
837 }
838 int timeout = (transfer_start == last_advance_time) ? m_first_timeout : m_timeout;
839 if (now > last_advance_time + timeout) {
840 const char *log_prefix = rec.log_prefix.c_str();
841 bool tpc_pull = strncmp("Pull", log_prefix, 4) == 0;
842
844 std::stringstream ss;
845 ss << "Transfer failed because no bytes have been "
846 << (tpc_pull ? "received from the source (pull mode) in "
847 : "transmitted to the destination (push mode) in ") << timeout << " seconds.";
848 state.SetErrorMessage(ss.str());
849 curl_multi_remove_handle(multi_handle, curl);
850 curl_multi_cleanup(multi_handle);
851 break;
852 }
853 last_marker = now;
854 }
855 // The transfer will start after this point, notify the packet marking manager
856 rec.pmarkManager.startTransfer();
857 mres = curl_multi_perform(multi_handle, &running_handles);
858 if (mres == CURLM_CALL_MULTI_PERFORM) {
859 // curl_multi_perform should be called again immediately. On newer
860 // versions of curl, this is no longer used.
861 continue;
862 } else if (mres != CURLM_OK) {
863 break;
864 } else if (running_handles == 0) {
865 break;
866 }
867
868 rec.pmarkManager.beginPMarks();
869 //printf("There are %d running handles\n", running_handles);
870
871 // Harvest any messages, looking for CURLMSG_DONE.
872 CURLMsg *msg;
873 do {
874 int msgq = 0;
875 msg = curl_multi_info_read(multi_handle, &msgq);
876 if (msg && (msg->msg == CURLMSG_DONE)) {
877 CURL *easy_handle = msg->easy_handle;
878 res = msg->data.result;
879 curl_multi_remove_handle(multi_handle, easy_handle);
880 }
881 } while (msg);
882
883 int64_t max_sleep_time = next_marker - time(NULL);
884 if (max_sleep_time <= 0) {
885 continue;
886 }
887 int fd_count;
888 mres = curl_multi_wait(multi_handle, NULL, 0, max_sleep_time*1000, &fd_count);
889 if (mres != CURLM_OK) {
890 break;
891 }
892 } while (running_handles);
893
894 if (mres != CURLM_OK) {
895 std::stringstream ss;
896 ss << "Internal libcurl multi-handle error: HTTP library failure=" << curl_multi_strerror(mres);
897 logTransferEvent(LogMask::Error, rec, "TRANSFER_CURL_ERROR", ss.str());
898
899 curl_multi_remove_handle(multi_handle, curl);
900 curl_multi_cleanup(multi_handle);
901
902 if ((retval = req.ChunkResp(generateClientErr(ss, rec).c_str(), 0))) {
903 logTransferEvent(LogMask::Error, rec, "RESPONSE_FAIL",
904 "Failed to send error message to the TPC client");
905 return retval;
906 }
907 return req.ChunkResp(NULL, 0);
908 }
909
910 // Harvest any messages, looking for CURLMSG_DONE.
911 CURLMsg *msg;
912 do {
913 int msgq = 0;
914 msg = curl_multi_info_read(multi_handle, &msgq);
915 if (msg && (msg->msg == CURLMSG_DONE)) {
916 CURL *easy_handle = msg->easy_handle;
917 res = msg->data.result;
918 curl_multi_remove_handle(multi_handle, easy_handle);
919 }
920 } while (msg);
921
922 if (!state.GetErrorCode() && res == static_cast<CURLcode>(-1)) { // No transfers returned?!?
923 curl_multi_remove_handle(multi_handle, curl);
924 curl_multi_cleanup(multi_handle);
925 std::stringstream ss;
926 ss << "Internal state error in libcurl";
927 logTransferEvent(LogMask::Error, rec, "TRANSFER_CURL_ERROR", ss.str());
928
929 if ((retval = req.ChunkResp(generateClientErr(ss, rec).c_str(), 0))) {
930 logTransferEvent(LogMask::Error, rec, "RESPONSE_FAIL",
931 "Failed to send error message to the TPC client");
932 return retval;
933 }
934 return req.ChunkResp(NULL, 0);
935 }
936 curl_multi_cleanup(multi_handle);
937
938 // The transfer is over at this point: any error recorded so far - a failed
939 // write to the local filesystem or the stall detector having fired - is the
940 // reason why the transfer failed. Flushing and closing the destination file
941 // below may fail as well but, as such a failure is usually a consequence of
942 // the transfer failure, it must not be reported instead of it.
943 const int transferErrorCode = state.GetErrorCode();
944 std::string transferErrorMsg = state.GetErrorMessage();
945
946 state.Flush();
947
948 rec.bytes_transferred = state.BytesTransferred();
949 rec.tpc_status = state.GetStatusCode();
950
951 // Explicitly finalize the stream (which will close the underlying file
952 // handle) before the response is sent. In some cases, subsequent HTTP
953 // requests can occur before the filesystem is done closing the handle -
954 // and those requests may occur against partial data.
955 state.Finalize();
956
957 // A failure to flush or to close the destination file is always logged and is
958 // appended to the error reported to the client, but it never replaces the
959 // transfer failure itself: it is usually a consequence of it.
960 std::string finalizeErrorMsg, finalizeErrorSuffix;
961 if (state.GetFinalizeErrorCode()) {
962 std::stringstream ss2;
963 ss2 << (state.GetFinalizeErrorCode() == State::errFlush
964 ? "Failed to flush the file to the local filesystem."
965 : "Failed to finalize and close file handle.");
966 std::string err = state.GetFinalizeErrorMessage();
967 if (!err.empty()) {
968 std::replace(err.begin(), err.end(), '\n', ' ');
969 ss2 << " " << err;
970 }
971 finalizeErrorMsg = ss2.str();
972 logTransferEvent(LogMask::Error, rec, "CLOSE_FAIL", finalizeErrorMsg);
973 finalizeErrorSuffix = "; " + finalizeErrorMsg;
974 }
975
976 // Generate the final response back to the client.
977 std::stringstream ss;
978 bool success = false;
979 if (state.GetStatusCode() >= 400) {
980 std::string err = state.GetErrorMessage();
981 std::stringstream ss2;
982 ss2 << "Remote side failed with status code " << state.GetStatusCode();
983 if (!err.empty()) {
984 std::replace(err.begin(), err.end(), '\n', ' ');
985 ss2 << "; error message: \"" << err << "\"";
986 }
987 logTransferEvent(LogMask::Error, rec, "TRANSFER_FAIL", ss2.str());
988 ss2 << finalizeErrorSuffix;
989 ss << generateClientErr(ss2, rec);
990 } else if (transferErrorCode == State::errTimeout) {
991 // The stall detector fired; its message already describes precisely
992 // what happened, report it as-is.
993 std::stringstream ss2;
994 ss2 << transferErrorMsg;
995 logTransferEvent(LogMask::Error, rec, "TRANSFER_FAIL", ss2.str());
996 ss2 << finalizeErrorSuffix;
997 ss << generateClientErr(ss2, rec);
998 } else if (transferErrorCode) {
999 if (transferErrorMsg.empty()) {transferErrorMsg = "(no error message provided)";}
1000 else {std::replace(transferErrorMsg.begin(), transferErrorMsg.end(), '\n', ' ');}
1001 std::stringstream ss2;
1002 ss2 << "Error when interacting with local filesystem: " << transferErrorMsg;
1003 logTransferEvent(LogMask::Error, rec, "TRANSFER_FAIL", ss2.str());
1004 ss2 << finalizeErrorSuffix;
1005 ss << generateClientErr(ss2, rec);
1006 } else if (res != CURLE_OK) {
1007 std::stringstream ss2;
1008 ss2 << "Internal transfer failure";
1009 std::stringstream ss3;
1010 ss3 << ss2.str() << ": " << curl_easy_strerror(res);
1011 logTransferEvent(LogMask::Error, rec, "TRANSFER_FAIL", ss3.str());
1012 ss2 << finalizeErrorSuffix;
1013 ss << generateClientErr(ss2, rec, res);
1014 } else if (!finalizeErrorMsg.empty()) {
1015 // Nothing else went wrong: the flush/close failure is the reason of the failure.
1016 std::stringstream ss2;
1017 ss2 << finalizeErrorMsg;
1018 ss << generateClientErr(ss2, rec);
1019 } else {
1020 ss << "success: Created";
1021 success = true;
1022 }
1023
1024 if ((retval = req.ChunkResp(ss.str().c_str(), 0))) {
1025 logTransferEvent(LogMask::Error, rec, "TRANSFER_ERROR",
1026 "Failed to send last update to remote client");
1027 return retval;
1028 } else if (success) {
1029 logTransferEvent(LogMask::Info, rec, "TRANSFER_SUCCESS");
1030 rec.status = 0;
1031 }
1032 return req.ChunkResp(NULL, 0);
1033}
1034
1035/******************************************************************************/
1036/* T P C H a n d l e r : : P r o c e s s P u s h R e q */
1037/******************************************************************************/
1038
1039int TPCHandler::ProcessPushReq(const std::string & resource, XrdHttpExtReq &req) {
1040 TPCLogRecord rec(req, TpcType::Push);
1041 rec.allow_local = m_allow_local;
1042 rec.allow_private = m_allow_private;
1043 rec.log_prefix = "PushRequest";
1044 rec.local = req.resource;
1045 rec.remote = resource;
1046 rec.m_log = &m_log;
1047 char *name = req.GetSecEntity().name;
1048 req.GetClientID(rec.clID);
1049 if (name) rec.name = name;
1050 logTransferEvent(LogMask::Info, rec, "PUSH_START", "Starting a push request");
1051
1052 ManagedCurlHandle curlPtr(curl_easy_init());
1053 auto curl = curlPtr.get();
1054 if (!curl) {
1055 std::stringstream ss;
1056 ss << "Failed to initialize internal transfer resources";
1057 rec.status = 500;
1058 logTransferEvent(LogMask::Error, rec, "PUSH_FAIL", ss.str());
1059 return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
1060 }
1061 ConfigureCurlLowSpeed(curl);
1062 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
1063 curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
1064 curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long) CURL_HTTP_VERSION_1_1);
1065#if CURL_AT_LEAST_VERSION(7, 85, 0)
1066 curl_easy_setopt(curl, CURLOPT_PROTOCOLS_STR, "https,http");
1067 curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS_STR, "https,http");
1068#else
1069 long protocols = CURLPROTO_HTTP | CURLPROTO_HTTPS;
1070 curl_easy_setopt(curl, CURLOPT_PROTOCOLS, protocols);
1071 curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, protocols);
1072#endif
1073 curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, opensocket_callback);
1074 curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, &rec);
1075 curl_easy_setopt(curl, CURLOPT_CLOSESOCKETFUNCTION, closesocket_callback);
1076 curl_easy_setopt(curl, CURLOPT_SOCKOPTFUNCTION, sockopt_callback);
1077 curl_easy_setopt(curl, CURLOPT_CLOSESOCKETDATA, &rec);
1078 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, CONNECT_TIMEOUT);
1079
1080 auto query_header = XrdOucTUtils::caseInsensitiveFind(req.headers,"xrd-http-fullresource");
1081 std::string redirect_resource = req.resource;
1082 if (query_header != req.headers.end()) {
1083 redirect_resource = query_header->second;
1084 }
1085
1086 AtomicBeg(m_monid_mutex);
1087 uint64_t file_monid = AtomicInc(m_monid);
1088 AtomicEnd(m_monid_mutex);
1089 std::unique_ptr<XrdSfsFile> fh(m_sfs->newFile(name, file_monid));
1090 if (!fh.get()) {
1091 rec.status = 500;
1092 std::stringstream ss;
1093 ss << "Failed to initialize internal transfer file handle";
1094 logTransferEvent(LogMask::Error, rec, "OPEN_FAIL",
1095 ss.str());
1096 return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
1097 }
1098 std::string full_url = prepareURL(req);
1099
1100 std::string authz = GetAuthz(req);
1101
1102 int open_results = OpenWaitStall(*fh, full_url, SFS_O_RDONLY, 0644,
1103 req.GetSecEntity(), authz);
1104 if (SFS_REDIRECT == open_results) {
1105 int result = RedirectTransfer(curl, redirect_resource, req, fh->error, rec);
1106 return result;
1107 } else if (SFS_OK != open_results) {
1108 int code;
1109 std::stringstream ss;
1110 const char *msg = fh->error.getErrText(code);
1111 if (msg == NULL) ss << "Failed to open local resource";
1112 else ss << msg;
1113 rec.status = mapErrNoToHttp(code);
1114 logTransferEvent(LogMask::Error, rec, "OPEN_FAIL", msg);
1115 int resp_result = req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
1116 fh->close();
1117 return resp_result;
1118 }
1119 if (!ConfigureCurlCA(curl, rec)) {
1120 std::stringstream ss;
1121 ss << "Failed to configure the certificate authorities for the transfer";
1122 rec.status = 500;
1123 logTransferEvent(LogMask::Error, rec, "PUSH_FAIL", ss.str());
1124 int resp_result = req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
1125 fh->close();
1126 return resp_result;
1127 }
1128 curl_easy_setopt(curl, CURLOPT_URL, resource.c_str());
1129
1130 Stream stream(std::move(fh), 0, 0, m_log);
1131 State state(0, stream, curl, true, req.tpcForwardCreds);
1132 state.SetupHeaders(req);
1133
1134 return RunCurlWithUpdates(curl, req, state, rec);
1135}
1136
1137/******************************************************************************/
1138/* T P C H a n d l e r : : P r o c e s s P u l l R e q */
1139/******************************************************************************/
1140
1141int TPCHandler::ProcessPullReq(const std::string &resource, XrdHttpExtReq &req) {
1142 TPCLogRecord rec(req,TpcType::Pull);
1143 rec.allow_local = m_allow_local;
1144 rec.allow_private = m_allow_private;
1145 rec.log_prefix = "PullRequest";
1146 rec.local = req.resource;
1147 rec.remote = resource;
1148 rec.m_log = &m_log;
1149 char *name = req.GetSecEntity().name;
1150 req.GetClientID(rec.clID);
1151 if (name) rec.name = name;
1152 logTransferEvent(LogMask::Info, rec, "PULL_START", "Starting a pull request");
1153
1154 ManagedCurlHandle curlPtr(curl_easy_init());
1155 auto curl = curlPtr.get();
1156 if (!curl) {
1157 std::stringstream ss;
1158 ss << "Failed to initialize internal transfer resources";
1159 rec.status = 500;
1160 logTransferEvent(LogMask::Error, rec, "PULL_FAIL", ss.str());
1161 return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
1162 }
1163 ConfigureCurlLowSpeed(curl);
1164
1165 // ddavila 2023-01-05:
1166 // The following change was required by the Rucio/SENSE project where
1167 // multiple IP addresses, each from a different subnet, are assigned to a
1168 // single server and routed differently by SENSE.
1169 // The above requires the server to utilize the same IP, that was used to
1170 // start the TPC, for the resolution of the given TPC instead of
1171 // using any of the IPs available.
1172 if (m_fixed_route) {
1173 char ip[64];
1174 char ipType = 0;
1175
1176 XrdNetAddrInfo *addrInfo = req.GetSecEntity().addrInfo;
1177 int sockFD = addrInfo ? addrInfo->SockFD() : -1;
1178
1179 if (sockFD < 0 || XrdNetUtils::GetSokInfo(-sockFD, ip, sizeof(ip), ipType) < 0) {
1180 // The socket information could not be fetched for some reason, treat this tpc.fixed_route as "best-effort" instead
1181 // of failing the transfer
1182 logTransferEvent(LogMask::Error, rec, "FIXED_ROUTE_ERR", "Failed to determine local address of incoming fixed route request");
1183 } else {
1184 logTransferEvent(LogMask::Info, rec, "LOCAL IP", ip);
1185 curl_easy_setopt(curl, CURLOPT_INTERFACE, ip);
1186 }
1187 }
1188 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
1189 curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
1190 curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long) CURL_HTTP_VERSION_1_1);
1191#if CURL_AT_LEAST_VERSION(7, 85, 0)
1192 curl_easy_setopt(curl, CURLOPT_PROTOCOLS_STR, "https,http");
1193 curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS_STR, "https,http");
1194#else
1195 long protocols = CURLPROTO_HTTP | CURLPROTO_HTTPS;
1196 curl_easy_setopt(curl, CURLOPT_PROTOCOLS, protocols);
1197 curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, protocols);
1198#endif
1199 curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, opensocket_callback);
1200 curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, &rec);
1201 curl_easy_setopt(curl, CURLOPT_SOCKOPTFUNCTION, sockopt_callback);
1202 curl_easy_setopt(curl, CURLOPT_SOCKOPTDATA , &rec);
1203 curl_easy_setopt(curl, CURLOPT_CLOSESOCKETFUNCTION, closesocket_callback);
1204 curl_easy_setopt(curl, CURLOPT_CLOSESOCKETDATA, &rec);
1205 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, CONNECT_TIMEOUT);
1206 std::unique_ptr<XrdSfsFile> fh(m_sfs->newFile(name, m_monid++));
1207 if (!fh.get()) {
1208 std::stringstream ss;
1209 ss << "Failed to initialize internal transfer file handle";
1210 rec.status = 500;
1211 logTransferEvent(LogMask::Error, rec, "PULL_FAIL", ss.str());
1212 return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
1213 }
1214 auto query_header = XrdOucTUtils::caseInsensitiveFind(req.headers,"xrd-http-fullresource");
1215 std::string redirect_resource = req.resource;
1216 if (query_header != req.headers.end()) {
1217 redirect_resource = query_header->second;
1218 }
1220 auto overwrite_header = XrdOucTUtils::caseInsensitiveFind(req.headers,"overwrite");
1221 if ((overwrite_header == req.headers.end()) || (overwrite_header->second == "T")) {
1222 if (! usingEC) mode = SFS_O_TRUNC;
1223 }
1224 int streams = 1;
1225 {
1226 auto streams_header = XrdOucTUtils::caseInsensitiveFind(req.headers,"x-number-of-streams");
1227 if (streams_header != req.headers.end()) {
1228 int stream_req = -1;
1229 try {
1230 stream_req = std::stol(streams_header->second);
1231 } catch (...) { // Handled below
1232 }
1233 if (stream_req < 0 || stream_req > 100) {
1234 std::stringstream ss;
1235 ss << "Invalid request for number of streams";
1236 rec.status = 400;
1237 logTransferEvent(LogMask::Info, rec, "INVALID_REQUEST", ss.str());
1238 return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
1239 }
1240 streams = stream_req == 0 ? 1 : stream_req;
1241 }
1242 }
1243 rec.streams = streams;
1244 std::string full_url = prepareURL(req);
1245 std::string authz = GetAuthz(req);
1246 curl_easy_setopt(curl, CURLOPT_URL, resource.c_str());
1247 if (!ConfigureCurlCA(curl, rec)) {
1248 std::stringstream ss;
1249 ss << "Failed to configure the certificate authorities for the transfer";
1250 rec.status = 500;
1251 logTransferEvent(LogMask::Error, rec, "PULL_FAIL", ss.str());
1252 return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
1253 }
1254 uint64_t sourceFileContentLength = 0;
1255 {
1256 //Get the content-length of the source file and pass it to the OSS layer
1257 //during the open
1258 bool success = false;
1259 bool mismatchDigests = false;
1260 std::map<std::string,std::string> sourceFileReprDigest;
1261 GetRemoteFileInfoTPCPull(curl, req, sourceFileContentLength, sourceFileReprDigest, success, rec);
1262 if(success) {
1263 //In the case we cannot get the information from the source server (offline or other error)
1264 //we just don't add the file information to the opaque of the local file to open
1265 full_url += "&oss.asize=" + std::to_string(sourceFileContentLength);
1266 mismatchDigests = mismatchReprDigest(sourceFileReprDigest,req,rec);
1267 }
1268 if(!success || mismatchDigests) {
1269 // We could not get remote file information, or the checksum provided by the client
1270 // does not match the source file one, we already sent the error to the client so we
1271 // just exit here
1272 return 0;
1273 }
1274 }
1275 int open_result = OpenWaitStall(*fh, full_url, mode|SFS_O_WRONLY,
1276 0644 | SFS_O_MKPTH,
1277 req.GetSecEntity(), authz);
1278 if (SFS_REDIRECT == open_result) {
1279 int result = RedirectTransfer(curl, redirect_resource, req, fh->error, rec);
1280 return result;
1281 } else if (SFS_OK != open_result) {
1282 int code;
1283 std::stringstream ss;
1284 const char *msg = fh->error.getErrText(code);
1285 if ((msg == NULL) || (*msg == '\0')) ss << "Failed to open local resource";
1286 else ss << msg;
1287 rec.status = mapErrNoToHttp(code);
1288 logTransferEvent(LogMask::Error, rec, "OPEN_FAIL", ss.str());
1289 int resp_result = req.SendSimpleResp(rec.status, NULL, NULL,
1290 generateClientErr(ss, rec).c_str(), 0);
1291 fh->close();
1292 return resp_result;
1293 }
1294 Stream stream(std::move(fh), streams * m_pipelining_multiplier, streams > 1 ? m_block_size : m_small_block_size, m_log);
1295 State state(0, stream, curl, false, req.tpcForwardCreds);
1296 state.SetupHeaders(req);
1297 state.SetContentLength(sourceFileContentLength);
1298
1299 if (streams > 1) {
1300 return RunCurlWithStreams(req, state, streams, rec);
1301 } else {
1302 return RunCurlWithUpdates(curl, req, state, rec);
1303 }
1304}
1305
1306/******************************************************************************/
1307/* T P C H a n d l e r : : l o g T r a n s f e r E v e n t */
1308/******************************************************************************/
1309
1310void TPCHandler::logTransferEvent(LogMask mask, const TPCLogRecord &rec,
1311 const std::string &event, const std::string &message)
1312{
1313 if (!(m_log.getMsgMask() & mask)) {return;}
1314
1315 std::stringstream ss;
1316 ss << "event=" << event << ", local=" << rec.local << ", remote=" << rec.remote;
1317 if (rec.name.empty())
1318 ss << ", user=(anonymous)";
1319 else
1320 ss << ", user=" << rec.name;
1321 if (rec.streams != 1)
1322 ss << ", streams=" << rec.streams;
1323 if (rec.bytes_transferred >= 0)
1324 ss << ", bytes_transferred=" << rec.bytes_transferred;
1325 if (rec.status >= 0)
1326 ss << ", status=" << rec.status;
1327 if (rec.tpc_status >= 0)
1328 ss << ", tpc_status=" << rec.tpc_status;
1329 if (!message.empty())
1330 ss << "; " << message;
1331 m_log.Log(mask, rec.log_prefix.c_str(), ss.str().c_str());
1332}
1333
1334std::string TPCHandler::generateClientErr(std::stringstream &err_ss, const TPCLogRecord &rec, CURLcode cCode) {
1335 std::stringstream ssret;
1336 ssret << "failure: " << err_ss.str() << ", local=" << rec.local <<", remote=" << rec.remote;
1337 if(cCode != CURLcode::CURLE_OK) {
1338 ssret << ", HTTP library failure=" << curl_easy_strerror(cCode);
1339 }
1340 return ssret.str();
1341}
1342/******************************************************************************/
1343/* X r d H t t p G e t E x t H a n d l e r */
1344/******************************************************************************/
1345
1346extern "C" {
1347
1348XrdHttpExtHandler *XrdHttpGetExtHandler(XrdSysError *log, const char * config, const char * /*parms*/, XrdOucEnv *myEnv) {
1349 if (curl_global_init(CURL_GLOBAL_DEFAULT)) {
1350 log->Emsg("TPCInitialize", "libcurl failed to initialize");
1351 return NULL;
1352 }
1353
1354 TPCHandler *retval{NULL};
1355 if (!config) {
1356 log->Emsg("TPCInitialize", "TPC handler requires a config filename in order to load");
1357 return NULL;
1358 }
1359 try {
1360 log->Emsg("TPCInitialize", "Will load configuration for the TPC handler from", config);
1361 retval = new TPCHandler(log, config, myEnv);
1362 } catch (std::runtime_error &re) {
1363 log->Emsg("TPCInitialize", "Encountered a runtime failure when loading ", re.what());
1364 //printf("Provided env vars: %p, XrdInet*: %p\n", myEnv, myEnv->GetPtr("XrdInet*"));
1365 }
1366 return retval;
1367}
1368
1369}
void CURL
XrdHttpExtHandler * XrdHttpGetExtHandler(XrdHttpExtHandlerArgs)
XrdVERSIONINFO(XrdHttpGetExtHandler, HttpTPC)
static std::string PrepareURL(const std::string &url)
std::string encode_xrootd_opaque_to_uri(CURL *curl, const std::string &opaque)
static bool IsAllowedScheme(const std::string &url)
int mapErrNoToHttp(int errNo)
std::string httpStatusToString(int status)
Utility functions for XrdHTTP.
std::string encode_str(const std::string &str)
void splitHostCgi(std::string_view target, std::string &host, std::string &cgi)
#define close(a)
Definition XrdPosix.hh:48
void getline(uchar *buff, int blen)
#define SFS_REDIRECT
#define SFS_O_MKPTH
#define SFS_STALL
#define SFS_O_RDONLY
#define SFS_STARTED
#define SFS_O_WRONLY
#define SFS_O_CREAT
int XrdSfsFileOpenMode
#define SFS_OK
#define SFS_O_TRUNC
#define AtomicInc(x)
#define AtomicBeg(Mtx)
#define AtomicEnd(Mtx)
if(Avsz)
const std::map< std::string, std::string > & GetReprDigest() const
int GetFinalizeErrorCode() const
int GetStatusCode() const
off_t BytesTransferred() const
void SetErrorMessage(const std::string &error_msg)
int GetErrorCode() const
std::string GetFinalizeErrorMessage() const
std::string GetErrorMessage() const
std::string GetConnectionDescription()
void SetupHeaders(XrdHttpExtReq &req)
void SetContentLength(const off_t content_length)
off_t GetContentLength() const
void SetErrorCode(int error_code)
void SetupHeadersForHEAD(XrdHttpExtReq &req)
TPCHandler(XrdSysError *log, const char *config, XrdOucEnv *myEnv)
virtual int ProcessReq(XrdHttpExtReq &req)
virtual ~TPCHandler()
virtual bool MatchesPath(const char *verb, const char *path)
Tells if the incoming path is recognized as one of the paths that have to be processed.
int ChunkResp(const char *body, long long bodylen)
Send a (potentially partial) body in a chunked response; invoking with NULL body.
void GetClientID(std::string &clid)
std::map< std::string, std::string > & headers
std::string resource
std::map< std::string, std::string > mReprDigest
Repr-Digest map where the key is the digest name and the value is the base64 encoded digest value.
int StartChunkedResp(int code, const char *desc, const char *header_to_add)
Starts a chunked response; body of request is sent over multiple parts using the SendChunkResp.
const XrdSecEntity & GetSecEntity() const
int SendSimpleResp(int code, const char *desc, const char *header_to_add, const char *body, long long bodylen)
Sends a basic response. If the length is < 0 then it is calculated internally.
static std::string prepareOpenURL(PrepareOpenURLParams &params)
static int GetSokInfo(int fd, char *theAddr, int theALen, char &theType)
void * GetPtr(const char *varname)
Definition XrdOucEnv.cc:263
const char * getErrText()
void setUCap(int ucval)
Set user capabilties.
static std::map< std::string, T >::const_iterator caseInsensitiveFind(const std::map< std::string, T > &m, const std::string &lowerCaseSearchKey)
XrdNetAddrInfo * addrInfo
Entity's connection details.
char * name
Entity's name.
XrdOucErrInfo & error
virtual int open(const char *fileName, XrdSfsFileOpenMode openMode, mode_t createMode, const XrdSecEntity *client=0, const char *opaque=0)=0
virtual int close()=0
int Emsg(const char *esfx, int ecode, const char *text1, const char *text2=0)
XrdSysLogger * logger(XrdSysLogger *lp=0)
static Outcome Redirect(const char *trg, int &port, XrdNetAddrInfo &clientAddr, std::string &outTarget, std::string &errMsg)
std::unique_ptr< CURL, CurlDeleter > ManagedCurlHandle
void operator()(CURL *curl)
static const int uIPv64
ucap: Supports only IPv4 info