9#include "XrdVersion.hh"
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;
43bool TPCHandler::allowMissingCRL =
false;
51TPCHandler::TPCLogRecord::~TPCLogRecord()
58 monInfo.
clID = clID.c_str();
60 gettimeofday(&monInfo.
endT, 0);
63 {monInfo.
dstURL = local.c_str();
64 monInfo.
srcURL = remote.c_str();
66 monInfo.
dstURL = remote.c_str();
67 monInfo.
srcURL = local.c_str();
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);
78 tpcMonitor->Report(monInfo);
88 if (curl) curl_easy_cleanup(curl);
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()) {
108 return CURL_SOCKOPT_ALREADY_CONNECTED;
110 return CURL_SOCKOPT_OK;
122int TPCHandler::opensocket_callback(
void *clientp,
123 curlsocktype purpose,
124 struct curl_sockaddr *aInfo)
128 if (purpose != CURLSOCKTYPE_IPCXN)
129 return CURL_SOCKET_BAD;
132 return CURL_SOCKET_BAD;
135 int fd = XrdSysFD_Socket(aInfo->family, aInfo->socktype, aInfo->protocol);
138 return CURL_SOCKET_BAD;
144 XrdNetAddr thePeer(&(aInfo->addr));
145 TPCLogRecord *rec =
static_cast<TPCLogRecord*
>(clientp);
148 if ((!rec->allow_private && thePeer.isPrivate()) || (!rec->allow_local && thePeer.isLocal())) {
149 rec->tpc_status = 403;
150 rec->m_log->Emsg(rec->log_prefix.c_str(),
151 "Connection to local/private address is forbidden");
153 return CURL_SOCKET_BAD;
158 std::stringstream connectErrMsg;
159 if(!rec->pmarkManager.connect(fd, &(aInfo->addr), aInfo->addrlen, CONNECT_TIMEOUT, connectErrMsg)) {
161 rec->m_log->Emsg(rec->log_prefix.c_str(),
"Unable to connect socket: ", connectErrMsg.str().c_str());
162 return CURL_SOCKET_BAD;
168int TPCHandler::closesocket_callback(
void *clientp, curl_socket_t fd) {
169 TPCLogRecord * rec = (TPCLogRecord *)clientp;
174 rec->pmarkManager.endPmark(fd);
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);
193 if (rec && rec->ca_store) {
197 SSL_CTX_set1_cert_store(ctx, rec->ca_store.get());
199 if (allowMissingCRL) {
203 SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback);
208int TPCHandler::verify_callback(
int preverify_ok, X509_STORE_CTX* ctx) {
209 if (preverify_ok == 1)
return 1;
211 int err = X509_STORE_CTX_get_error(ctx);
213 if (err == X509_V_ERR_UNABLE_TO_GET_CRL) {
214 X509_STORE_CTX_set_error(ctx, X509_V_OK);
226std::string TPCHandler::prepareURL(XrdHttpExtReq &req) {
231bool TPCHandler::mismatchReprDigest(
const std::map<std::string, std::string> & passiveSrvReprDigest, XrdHttpExtReq &req,
233 if(passiveSrvReprDigest.size()) {
234 for (
const auto & [digestName, digestValue]: passiveSrvReprDigest) {
235 auto clientDigestMatch = req.
mReprDigest.find(digestName);
238 if (clientDigestMatch->second != digestValue) {
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());
246 req.
SendSimpleResp(rec.status, NULL, NULL, generateClientErr(errMsg, rec, CURLcode::CURLE_OK).c_str(), 0);
266 std::stringstream parser(opaque);
267 std::string sequence;
268 std::stringstream output;
270 while (
getline(parser, sequence,
'&')) {
271 if (sequence.empty()) {
continue;}
272 size_t equal_pos = sequence.find(
'=');
274 if (equal_pos != std::string::npos)
275 val = curl_easy_escape(curl, sequence.c_str() + equal_pos + 1, sequence.size() - equal_pos - 1);
277 if (!val && equal_pos != std::string::npos) {
continue;}
279 if (!first) output <<
"&";
281 output << sequence.substr(0, equal_pos);
283 output <<
"=" << val;
295TPCHandler::ConfigureCurlCA(
CURL *curl, TPCLogRecord &rec)
305 if (m_ca_file && m_sslctx_supported && m_cafile.empty()) {
306 rec.ca_store = m_ca_file->CAStore();
308 m_log.Log(
Error,
"TpcHandler",
"No CA store is available; refusing to "
309 "fall back to libcurl's default CA bundle");
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);
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());
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) {
333 curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, ssl_ctx_callback);
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());
341 else if (!m_cadir.empty()) {
342 curl_easy_setopt(curl, CURLOPT_CAPATH, m_cadir.c_str());
344 if (!m_cafile.empty()) {
345 curl_easy_setopt(curl, CURLOPT_CAINFO, m_cafile.c_str());
351TPCHandler::ConfigureCurlLowSpeed(
CURL *curl)
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);
365 return !strcmp(verb,
"COPY") || !strcmp(verb,
"OPTIONS");
374 const std::string replace_schemes[] = {
"davs://",
"s3://",
"s3s://" };
376 for (
const auto& s : replace_schemes)
377 if (url.compare(0, s.size(), s) == 0)
378 return "https://" + url.substr(s.size());
385 const std::string allowed_schemes[] = {
"https://",
"http://" };
387 for (
const auto& s : allowed_schemes)
388 if (url.compare(0, s.size(), s) == 0)
399 if (req.
verb ==
"OPTIONS") {
400 return ProcessOptionsReq(req);
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);
414 if (srcHeader != req.
headers.end() && dstHeader != req.
headers.end()) {
415 const char *error_both =
"COPY rejected: both a Source and a Destination header were specified";
416 m_log.Emsg(
"ProcessReq", error_both);
419 if (srcHeader != req.
headers.end()) {
420 std::string src =
PrepareURL(srcHeader->second);
422 const char *error_src =
"COPY rejected: disallowed scheme in source URL";
423 m_log.Emsg(
"ProcessReq", error_src, src.c_str());
426 return ProcessPullReq(src, req);
428 if (dstHeader != req.
headers.end()) {
429 const std::string& dst = dstHeader->second;
431 const char *error_dst =
"COPY rejected: disallowed scheme in destination URL";
432 m_log.Emsg(
"ProcessReq", error_dst, dst.c_str());
435 return ProcessPushReq(dst, req);
437 m_log.Emsg(
"ProcessReq",
"COPY verb requested but no source or destination specified.");
438 return req.
SendSimpleResp(400, NULL, NULL,
"No Source or Destination specified", 0);
454 m_allow_local(false),
455 m_allow_private(true),
457 m_fixed_route(false),
458 m_low_speed_limit(10*1024),
459 m_low_speed_time(2*60),
461 m_first_timeout(120),
462 m_log(log->logger(),
"TPC_"),
465 if (!Configure(config, myEnv)) {
466 throw std::runtime_error(
"Failed to configure the HTTP third-party-copy handler.");
484 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);
494 if (authz_header != req.
headers.end()) {
495 std::stringstream ss;
496 ss <<
"authz=" <<
encode_str(authz_header->second);
506int TPCHandler::RedirectTransfer(
CURL *curl,
const std::string &redirect_resource,
507 XrdHttpExtReq &req, XrdOucErrInfo &error, TPCLogRecord &rec)
511 if ((ptr == NULL) || (*ptr ==
'\0') || (port == 0)) {
513 std::stringstream ss;
514 ss <<
"Internal error: redirect without hostname";
515 logTransferEvent(
LogMask::Error, rec,
"REDIRECT_INTERNAL_ERROR", ss.str());
516 return req.
SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
522 std::string finalTarget = ptr;
534 std::string newTarget;
539 finalTarget = std::move(newTarget);
541 logTransferEvent(
LogMask::Info, rec,
"REDIRECT_PLUGIN_REWRITE",
545 std::stringstream ess;
546 ess <<
"Redirect plugin error: " << errMsg;
550 generateClientErr(ess, rec).c_str(), 0);
562 std::string opaque = cgi.empty() ? std::string() : cgi.substr(1);
564 std::stringstream ss;
565 ss <<
"Location: http" << (m_desthttps ?
"s" :
"") <<
"://" << host <<
":" << port <<
"/" << redirect_resource;
567 if (!opaque.empty()) {
571 char sep = (redirect_resource.find(
'?') == std::string::npos) ?
'?' :
'&';
572 ss << sep << encode_xrootd_opaque_to_uri(curl, opaque);
577 return req.
SendSimpleResp(rec.status, NULL,
const_cast<char *
>(ss.str().c_str()),
585int TPCHandler::OpenWaitStall(XrdSfsFile &fh,
const std::string &resource,
586 int mode,
int openMode,
const XrdSecEntity &sec,
587 const std::string &authz)
594 size_t pos = resource.find(
'?');
596 std::string path = resource.substr(0, pos);
598 if (pos != std::string::npos) {
599 opaque = resource.substr(pos + 1);
604 opaque += (opaque.empty() ?
"" :
"&");
607 open_result = fh.
open(path.c_str(), mode, openMode, &sec, opaque.c_str());
611 if (open_result ==
SFS_STARTED) {secs_to_stall = secs_to_stall/2 + 5;}
612 std::this_thread::sleep_for (std::chrono::seconds(secs_to_stall));
628int TPCHandler::PerformHEADRequest(
CURL *curl, XrdHttpExtReq &req,
State &state,
629 bool &success, TPCLogRecord &rec,
bool shouldReturnErrorToClient) {
631 curl_easy_setopt(curl, CURLOPT_NOBODY, 1);
633 curl_easy_setopt(curl, CURLOPT_TIMEOUT, CONNECT_TIMEOUT);
635 curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, 1L);
637 res = curl_easy_perform(curl);
640 curl_easy_setopt(curl, CURLOPT_NOBODY, 0);
642 curl_easy_setopt(curl, CURLOPT_TIMEOUT, 0L);
644 curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, 0L);
645 curl_easy_setopt(curl, CURLOPT_FAILONERROR,
true);
647 std::stringstream ss;
650 res = CURLE_HTTP_RETURNED_ERROR;
652 if (res != CURLE_OK) {
653 ss << curl_easy_strerror(res);
655 case CURLE_HTTP_RETURNED_ERROR:
657 ss <<
": remote host returned '" << rec.tpc_status <<
" "
660 case CURLE_COULDNT_CONNECT:
661 switch (rec.tpc_status) {
663 ss <<
": connection to local/private addresses is forbidden";
666 ss <<
": internal server failure";
667 rec.tpc_status = 500;
671 rec.tpc_status = 500;
676 if (rec.tpc_status >= 400) {
678 return shouldReturnErrorToClient ? req.
SendSimpleResp(rec.tpc_status, NULL, NULL, generateClientErr(ss, rec, res).c_str(), 0) : -1;
682 ss <<
"Successfully determined remote file information for pull request: "
685 unsigned int cksumIndex = 1;
686 for(
const auto & [cksumType,cksumValue]: state.
GetReprDigest()) {
687 ss <<
" chksum" << cksumIndex <<
"=(" << cksumType <<
"," << cksumValue <<
")";
695int TPCHandler::GetRemoteFileInfoTPCPull(
CURL *curl, XrdHttpExtReq &req, uint64_t &contentLength, std::map<std::string,std::string> & reprDigest,
bool & success, TPCLogRecord &rec) {
702 if ((result = PerformHEADRequest(curl, req, state, success, rec)) || !success) {
714int TPCHandler::SendPerfMarker(XrdHttpExtReq &req, TPCLogRecord &rec, TPC::State &state) {
715 std::stringstream ss;
716 const std::string crlf =
"\n";
717 ss <<
"Perf Marker" << crlf;
718 ss <<
"Timestamp: " << time(NULL) << crlf;
719 ss <<
"Stripe Index: 0" << crlf;
721 ss <<
"Total Stripe Count: 1" << crlf;
726 ss <<
"RemoteConnections: " << desc << crlf;
731 return req.
ChunkResp(ss.str().c_str(), 0);
738int TPCHandler::SendPerfMarker(XrdHttpExtReq &req, TPCLogRecord &rec, std::vector<State*> &state,
739 off_t bytes_transferred)
753 std::stringstream ss;
754 const std::string crlf =
"\n";
755 ss <<
"Perf Marker" << crlf;
756 ss <<
"Timestamp: " << time(NULL) << crlf;
757 ss <<
"Stripe Index: 0" << crlf;
758 ss <<
"Stripe Bytes Transferred: " << bytes_transferred << crlf;
759 ss <<
"Total Stripe Count: 1" << crlf;
763 std::stringstream ss2;
764 for (std::vector<State*>::const_iterator iter = state.begin();
765 iter != state.end(); iter++)
767 std::string desc = (*iter)->GetConnectionDescription();
769 ss2 << (first ?
"" :
",") << desc;
774 ss <<
"RemoteConnections: " << ss2.str() << crlf;
776 rec.bytes_transferred = bytes_transferred;
779 return req.
ChunkResp(ss.str().c_str(), 0);
786int TPCHandler::RunCurlWithUpdates(
CURL *curl, XrdHttpExtReq &req,
State &state,
790 CURLM *multi_handle = curl_multi_init();
794 "Failed to initialize a libcurl multi-handle");
795 std::stringstream ss;
796 ss <<
"Failed to initialize internal server memory";
797 return req.
SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
803 mres = curl_multi_add_handle(multi_handle, curl);
806 std::stringstream ss;
807 ss <<
"Failed to add transfer to libcurl multi-handle: HTTP library failure=" << curl_multi_strerror(mres);
808 logTransferEvent(
LogMask::Error, rec,
"CURL_INIT_FAIL", ss.str());
809 curl_multi_cleanup(multi_handle);
810 return req.
SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
816 curl_multi_cleanup(multi_handle);
818 "Failed to send the initial response to the TPC client");
822 "Initial transfer response sent to the TPC client");
827 int running_handles = 1;
828 time_t last_marker = 0;
830 off_t last_advance_bytes = 0;
831 time_t last_advance_time = time(NULL);
832 time_t transfer_start = last_advance_time;
833 CURLcode res =
static_cast<CURLcode
>(-1);
835 time_t now = time(NULL);
836 time_t next_marker = last_marker + m_marker_period;
837 if (now >= next_marker) {
839 if (bytes_xfer > last_advance_bytes) {
840 last_advance_bytes = bytes_xfer;
841 last_advance_time = now;
843 if (SendPerfMarker(req, rec, state)) {
844 curl_multi_remove_handle(multi_handle, curl);
845 curl_multi_cleanup(multi_handle);
847 "Failed to send a perf marker to the TPC client");
850 int timeout = (transfer_start == last_advance_time) ? m_first_timeout : m_timeout;
851 if (now > last_advance_time + timeout) {
852 const char *log_prefix = rec.log_prefix.c_str();
853 bool tpc_pull = strncmp(
"Pull", log_prefix, 4) == 0;
856 std::stringstream ss;
857 ss <<
"Transfer failed because no bytes have been "
858 << (tpc_pull ?
"received from the source (pull mode) in "
859 :
"transmitted to the destination (push mode) in ") << timeout <<
" seconds.";
861 curl_multi_remove_handle(multi_handle, curl);
862 curl_multi_cleanup(multi_handle);
868 rec.pmarkManager.startTransfer();
869 mres = curl_multi_perform(multi_handle, &running_handles);
870 if (mres == CURLM_CALL_MULTI_PERFORM) {
874 }
else if (mres != CURLM_OK) {
876 }
else if (running_handles == 0) {
880 rec.pmarkManager.beginPMarks();
887 msg = curl_multi_info_read(multi_handle, &msgq);
888 if (msg && (msg->msg == CURLMSG_DONE)) {
889 CURL *easy_handle = msg->easy_handle;
890 res = msg->data.result;
891 curl_multi_remove_handle(multi_handle, easy_handle);
895 int64_t max_sleep_time = next_marker - time(NULL);
896 if (max_sleep_time <= 0) {
900 mres = curl_multi_wait(multi_handle, NULL, 0, max_sleep_time*1000, &fd_count);
901 if (mres != CURLM_OK) {
904 }
while (running_handles);
906 if (mres != CURLM_OK) {
907 std::stringstream ss;
908 ss <<
"Internal libcurl multi-handle error: HTTP library failure=" << curl_multi_strerror(mres);
909 logTransferEvent(
LogMask::Error, rec,
"TRANSFER_CURL_ERROR", ss.str());
911 curl_multi_remove_handle(multi_handle, curl);
912 curl_multi_cleanup(multi_handle);
914 if ((retval = req.
ChunkResp(generateClientErr(ss, rec).c_str(), 0))) {
916 "Failed to send error message to the TPC client");
926 msg = curl_multi_info_read(multi_handle, &msgq);
927 if (msg && (msg->msg == CURLMSG_DONE)) {
928 CURL *easy_handle = msg->easy_handle;
929 res = msg->data.result;
930 curl_multi_remove_handle(multi_handle, easy_handle);
934 if (!state.
GetErrorCode() && res ==
static_cast<CURLcode
>(-1)) {
935 curl_multi_remove_handle(multi_handle, curl);
936 curl_multi_cleanup(multi_handle);
937 std::stringstream ss;
938 ss <<
"Internal state error in libcurl";
939 logTransferEvent(
LogMask::Error, rec,
"TRANSFER_CURL_ERROR", ss.str());
941 if ((retval = req.
ChunkResp(generateClientErr(ss, rec).c_str(), 0))) {
943 "Failed to send error message to the TPC client");
948 curl_multi_cleanup(multi_handle);
972 std::string finalizeErrorMsg, finalizeErrorSuffix;
974 std::stringstream ss2;
976 ?
"Failed to flush the file to the local filesystem."
977 :
"Failed to finalize and close file handle.");
980 std::replace(err.begin(), err.end(),
'\n',
' ');
983 finalizeErrorMsg = ss2.str();
984 logTransferEvent(
LogMask::Error, rec,
"CLOSE_FAIL", finalizeErrorMsg);
985 finalizeErrorSuffix =
"; " + finalizeErrorMsg;
989 std::stringstream ss;
990 bool success =
false;
993 std::stringstream ss2;
994 ss2 <<
"Remote side failed with status code " << state.
GetStatusCode();
996 std::replace(err.begin(), err.end(),
'\n',
' ');
997 ss2 <<
"; error message: \"" << err <<
"\"";
999 logTransferEvent(
LogMask::Error, rec,
"TRANSFER_FAIL", ss2.str());
1000 ss2 << finalizeErrorSuffix;
1001 ss << generateClientErr(ss2, rec);
1005 std::stringstream ss2;
1006 ss2 << transferErrorMsg;
1007 logTransferEvent(
LogMask::Error, rec,
"TRANSFER_FAIL", ss2.str());
1008 ss2 << finalizeErrorSuffix;
1009 ss << generateClientErr(ss2, rec);
1010 }
else if (transferErrorCode) {
1011 if (transferErrorMsg.empty()) {transferErrorMsg =
"(no error message provided)";}
1012 else {std::replace(transferErrorMsg.begin(), transferErrorMsg.end(),
'\n',
' ');}
1013 std::stringstream ss2;
1014 ss2 <<
"Error when interacting with local filesystem: " << transferErrorMsg;
1015 logTransferEvent(
LogMask::Error, rec,
"TRANSFER_FAIL", ss2.str());
1016 ss2 << finalizeErrorSuffix;
1017 ss << generateClientErr(ss2, rec);
1018 }
else if (res != CURLE_OK) {
1019 std::stringstream ss2;
1020 ss2 <<
"Internal transfer failure";
1021 std::stringstream ss3;
1022 ss3 << ss2.str() <<
": " << curl_easy_strerror(res);
1023 logTransferEvent(
LogMask::Error, rec,
"TRANSFER_FAIL", ss3.str());
1024 ss2 << finalizeErrorSuffix;
1025 ss << generateClientErr(ss2, rec, res);
1026 }
else if (!finalizeErrorMsg.empty()) {
1028 std::stringstream ss2;
1029 ss2 << finalizeErrorMsg;
1030 ss << generateClientErr(ss2, rec);
1032 ss <<
"success: Created";
1036 if ((retval = req.
ChunkResp(ss.str().c_str(), 0))) {
1038 "Failed to send last update to remote client");
1040 }
else if (success) {
1051int TPCHandler::ProcessPushReq(
const std::string & resource, XrdHttpExtReq &req) {
1053 rec.allow_local = m_allow_local;
1054 rec.allow_private = m_allow_private;
1055 rec.log_prefix =
"PushRequest";
1057 rec.remote = resource;
1061 if (name) rec.name = name;
1062 logTransferEvent(
LogMask::Info, rec,
"PUSH_START",
"Starting a push request");
1065 auto curl = curlPtr.get();
1067 std::stringstream ss;
1068 ss <<
"Failed to initialize internal transfer resources";
1071 return req.
SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
1073 ConfigureCurlLowSpeed(curl);
1074 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
1075 curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
1076 curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (
long) CURL_HTTP_VERSION_1_1);
1077#if CURL_AT_LEAST_VERSION(7, 85, 0)
1078 curl_easy_setopt(curl, CURLOPT_PROTOCOLS_STR,
"https,http");
1079 curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS_STR,
"https,http");
1081 long protocols = CURLPROTO_HTTP | CURLPROTO_HTTPS;
1082 curl_easy_setopt(curl, CURLOPT_PROTOCOLS, protocols);
1083 curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, protocols);
1085 curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, opensocket_callback);
1086 curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, &rec);
1087 curl_easy_setopt(curl, CURLOPT_CLOSESOCKETFUNCTION, closesocket_callback);
1088 curl_easy_setopt(curl, CURLOPT_SOCKOPTFUNCTION, sockopt_callback);
1089 curl_easy_setopt(curl, CURLOPT_CLOSESOCKETDATA, &rec);
1090 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, CONNECT_TIMEOUT);
1093 std::string redirect_resource = req.
resource;
1094 if (query_header != req.
headers.end()) {
1095 redirect_resource = query_header->second;
1099 uint64_t file_monid =
AtomicInc(m_monid);
1101 std::unique_ptr<XrdSfsFile> fh(m_sfs->newFile(name, file_monid));
1104 std::stringstream ss;
1105 ss <<
"Failed to initialize internal transfer file handle";
1108 return req.
SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
1110 std::string full_url = prepareURL(req);
1112 std::string authz = GetAuthz(req);
1114 int open_results = OpenWaitStall(*fh, full_url,
SFS_O_RDONLY, 0644,
1117 int result = RedirectTransfer(curl, redirect_resource, req, fh->
error, rec);
1119 }
else if (
SFS_OK != open_results) {
1121 std::stringstream ss;
1123 if (msg == NULL) ss <<
"Failed to open local resource";
1127 int resp_result = req.
SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
1131 if (!ConfigureCurlCA(curl, rec)) {
1132 std::stringstream ss;
1133 ss <<
"Failed to configure the certificate authorities for the transfer";
1136 int resp_result = req.
SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
1140 curl_easy_setopt(curl, CURLOPT_URL, resource.c_str());
1142 Stream stream(std::move(fh), 0, 0, m_log);
1146 return RunCurlWithUpdates(curl, req, state, rec);
1153int TPCHandler::ProcessPullReq(
const std::string &resource, XrdHttpExtReq &req) {
1155 rec.allow_local = m_allow_local;
1156 rec.allow_private = m_allow_private;
1157 rec.log_prefix =
"PullRequest";
1159 rec.remote = resource;
1163 if (name) rec.name = name;
1164 logTransferEvent(
LogMask::Info, rec,
"PULL_START",
"Starting a pull request");
1167 auto curl = curlPtr.get();
1169 std::stringstream ss;
1170 ss <<
"Failed to initialize internal transfer resources";
1173 return req.
SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
1175 ConfigureCurlLowSpeed(curl);
1184 if (m_fixed_route) {
1189 int sockFD = addrInfo ? addrInfo->
SockFD() : -1;
1194 logTransferEvent(
LogMask::Error, rec,
"FIXED_ROUTE_ERR",
"Failed to determine local address of incoming fixed route request");
1197 curl_easy_setopt(curl, CURLOPT_INTERFACE, ip);
1200 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
1201 curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
1202 curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (
long) CURL_HTTP_VERSION_1_1);
1203#if CURL_AT_LEAST_VERSION(7, 85, 0)
1204 curl_easy_setopt(curl, CURLOPT_PROTOCOLS_STR,
"https,http");
1205 curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS_STR,
"https,http");
1207 long protocols = CURLPROTO_HTTP | CURLPROTO_HTTPS;
1208 curl_easy_setopt(curl, CURLOPT_PROTOCOLS, protocols);
1209 curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, protocols);
1211 curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, opensocket_callback);
1212 curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, &rec);
1213 curl_easy_setopt(curl, CURLOPT_SOCKOPTFUNCTION, sockopt_callback);
1214 curl_easy_setopt(curl, CURLOPT_SOCKOPTDATA , &rec);
1215 curl_easy_setopt(curl, CURLOPT_CLOSESOCKETFUNCTION, closesocket_callback);
1216 curl_easy_setopt(curl, CURLOPT_CLOSESOCKETDATA, &rec);
1217 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, CONNECT_TIMEOUT);
1218 std::unique_ptr<XrdSfsFile> fh(m_sfs->newFile(name, m_monid++));
1220 std::stringstream ss;
1221 ss <<
"Failed to initialize internal transfer file handle";
1224 return req.
SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
1227 std::string redirect_resource = req.
resource;
1228 if (query_header != req.
headers.end()) {
1229 redirect_resource = query_header->second;
1233 if ((overwrite_header == req.
headers.end()) || (overwrite_header->second ==
"T")) {
1239 if (streams_header != req.
headers.end()) {
1240 int stream_req = -1;
1242 stream_req = std::stol(streams_header->second);
1245 if (stream_req < 0 || stream_req > 100) {
1246 std::stringstream ss;
1247 ss <<
"Invalid request for number of streams";
1249 logTransferEvent(
LogMask::Info, rec,
"INVALID_REQUEST", ss.str());
1250 return req.
SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
1252 streams = stream_req == 0 ? 1 : stream_req;
1255 rec.streams = streams;
1256 std::string full_url = prepareURL(req);
1257 std::string authz = GetAuthz(req);
1258 curl_easy_setopt(curl, CURLOPT_URL, resource.c_str());
1259 if (!ConfigureCurlCA(curl, rec)) {
1260 std::stringstream ss;
1261 ss <<
"Failed to configure the certificate authorities for the transfer";
1264 return req.
SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
1266 uint64_t sourceFileContentLength = 0;
1270 bool success =
false;
1271 bool mismatchDigests =
false;
1272 std::map<std::string,std::string> sourceFileReprDigest;
1273 GetRemoteFileInfoTPCPull(curl, req, sourceFileContentLength, sourceFileReprDigest, success, rec);
1277 full_url +=
"&oss.asize=" + std::to_string(sourceFileContentLength);
1278 mismatchDigests = mismatchReprDigest(sourceFileReprDigest,req,rec);
1280 if(!success || mismatchDigests) {
1287 int open_result = OpenWaitStall(*fh, full_url, mode|
SFS_O_WRONLY,
1291 int result = RedirectTransfer(curl, redirect_resource, req, fh->
error, rec);
1293 }
else if (
SFS_OK != open_result) {
1295 std::stringstream ss;
1297 if ((msg == NULL) || (*msg ==
'\0')) ss <<
"Failed to open local resource";
1302 generateClientErr(ss, rec).c_str(), 0);
1306 Stream stream(std::move(fh), streams * m_pipelining_multiplier, streams > 1 ? m_block_size : m_small_block_size, m_log);
1312 return RunCurlWithStreams(req, state, streams, rec);
1316 curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, 1L);
1317 return RunCurlWithUpdates(curl, req, state, rec);
1325void TPCHandler::logTransferEvent(
LogMask mask,
const TPCLogRecord &rec,
1326 const std::string &event,
const std::string &message)
1328 if (!(m_log.getMsgMask() & mask)) {
return;}
1330 std::stringstream ss;
1331 ss <<
"event=" <<
event <<
", local=" << rec.local <<
", remote=" << rec.remote;
1332 if (rec.name.empty())
1333 ss <<
", user=(anonymous)";
1335 ss <<
", user=" << rec.name;
1336 if (rec.streams != 1)
1337 ss <<
", streams=" << rec.streams;
1338 if (rec.bytes_transferred >= 0)
1339 ss <<
", bytes_transferred=" << rec.bytes_transferred;
1340 if (rec.status >= 0)
1341 ss <<
", status=" << rec.status;
1342 if (rec.tpc_status >= 0)
1343 ss <<
", tpc_status=" << rec.tpc_status;
1344 if (!message.empty())
1345 ss <<
"; " << message;
1346 m_log.Log(mask, rec.log_prefix.c_str(), ss.str().c_str());
1349std::string TPCHandler::generateClientErr(std::stringstream &err_ss,
const TPCLogRecord &rec, CURLcode cCode) {
1350 std::stringstream ssret;
1351 ssret <<
"failure: " << err_ss.str() <<
", local=" << rec.local <<
", remote=" << rec.remote;
1352 if(cCode != CURLcode::CURLE_OK) {
1353 ssret <<
", HTTP library failure=" << curl_easy_strerror(cCode);
1365 if (curl_global_init(CURL_GLOBAL_DEFAULT)) {
1366 log->
Emsg(
"TPCInitialize",
"libcurl failed to initialize");
1372 log->
Emsg(
"TPCInitialize",
"TPC handler requires a config filename in order to load");
1376 log->
Emsg(
"TPCInitialize",
"Will load configuration for the TPC handler from", config);
1378 }
catch (std::runtime_error &re) {
1379 log->
Emsg(
"TPCInitialize",
"Encountered a runtime failure when loading ", re.what());
XrdVERSIONINFO(XrdClGetPlugIn, XrdClGetPlugIn) extern "C"
XrdHttpExtHandler * XrdHttpGetExtHandler(XrdHttpExtHandlerArgs)
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)
void getline(uchar *buff, int blen)
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)
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 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::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 ¶ms)
static int GetSokInfo(int fd, char *theAddr, int theALen, char &theType)
void * GetPtr(const char *varname)
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.
virtual int open(const char *fileName, XrdSfsFileOpenMode openMode, mode_t createMode, const XrdSecEntity *client=0, const char *opaque=0)=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)
XrdXrootdTpcMon(const char *proto, XrdSysLogger *logP, XrdXrootdGStream &gStrm)
std::unique_ptr< CURL, CurlDeleter > ManagedCurlHandle
void operator()(CURL *curl)
static const int uIPv64
ucap: Supports only IPv4 info