// This file is part of BOINC. // http://boinc.berkeley.edu // Copyright (C) 2008 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License // as published by the Free Software Foundation, // either version 3 of the License, or (at your option) any later version. // // BOINC is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with BOINC. If not, see . #include "cpp.h" #ifdef _WIN32 #include "boinc_win.h" #ifdef _MSC_VER #define unlink _unlink #define chdir _chdir #endif #else #include "config.h" #include #include #include #include #include #include #if HAVE_SYS_SOCKET_H #include #endif #endif #include "error_numbers.h" #include "filesys.h" #include "client_msgs.h" #include "log_flags.h" #include "str_util.h" #include "str_replace.h" #include "url.h" #include "util.h" #include "network.h" #include "file_names.h" #include "client_msgs.h" #include "base64.h" #include "client_state.h" #include "cs_proxy.h" #include "net_stats.h" #include "http_curl.h" using std::min; using std::vector; static CURLM* g_curlMulti = NULL; static char g_user_agent_string[256] = {""}; static const char g_content_type[] = {"Content-Type: application/x-www-form-urlencoded"}; static unsigned int g_trace_count = 0; char* get_user_agent_string() { sprintf(g_user_agent_string, "BOINC client (%s %d.%d.%d)", gstate.get_primary_platform(), BOINC_MAJOR_VERSION, BOINC_MINOR_VERSION, BOINC_RELEASE ); return (char*)&g_user_agent_string; } size_t libcurl_write(void *ptr, size_t size, size_t nmemb, HTTP_OP* phop) { // take the stream param as a FILE* and write to disk // TODO: maybe assert stRead == size*nmemb, // add exception handling on phop members // size_t stWrite = fwrite(ptr, size, nmemb, phop->fileOut); if (log_flags.http_xfer_debug) { msg_printf(NULL, MSG_INFO, "[http_xfer] [ID#%d] HTTP: wrote %d bytes", phop->trace_id, (int)stWrite ); } phop->bytes_xferred += (double)(stWrite); phop->update_speed(); // this should update the transfer speed daily_xfer_history.add(stWrite, false); return stWrite; } size_t libcurl_read(void *ptr, size_t size, size_t nmemb, HTTP_OP* phop) { // OK here's the deal -- phop points to the calling object, // which has already pre-opened the file. we'll want to // use pByte as a pointer for fseek calls into the file, and // write out size*nmemb # of bytes to ptr // take the stream param as a FILE* and write to disk // if (pByte) delete [] pByte; // pByte = new unsigned char[content_length]; // memset(pByte, 0x00, content_length); // may as will initialize it! // note that fileIn was opened earlier, // go to lSeek from the top and read from there // size_t stSend = size * nmemb; int stRead = 0; if (phop->req1 && !phop->bSentHeader) { // need to send headers first, then data file // so requests from 0 to strlen(req1)-1 are from memory, // and from strlen(req1) to content_length are from the file if (phop->lSeek < (long) strlen(phop->req1)) { // need to read header, either just starting to read // (i.e. this is the first time in this function for this phop) // or the last read didn't ask for the entire header stRead = (int)strlen(phop->req1) - phop->lSeek; // how much of header left to read // only memcpy if request isn't out of bounds if (stRead < 0) { stRead = 0; } else { memcpy(ptr, (void*)(phop->req1 + phop->lSeek), stRead); } phop->lSeek += (long) stRead; // increment lSeek to new position // Don't count header in bytes transferred. // Otherwise the GUI will show e.g. "400 out of 300 bytes xferred" //phop->bytes_xferred += (double)(stRead); daily_xfer_history.add(stRead, true); // see if we're done with headers if (phop->lSeek >= (long) strlen(phop->req1)) { phop->bSentHeader = true; phop->lSeek = 0; } return stRead; } else { // shouldn't happen phop->bSentHeader = true; phop->lSeek = 0; } } if (phop->fileIn) { long lFileSeek = phop->lSeek + (long) phop->file_offset; fseek(phop->fileIn, lFileSeek, SEEK_SET); if (!feof(phop->fileIn)) { stRead = (int)fread(ptr, 1, stSend, phop->fileIn); } phop->lSeek += (long) stRead; phop->bytes_xferred += (double)(stRead); daily_xfer_history.add(stRead, true); } phop->update_speed(); return stRead; } curlioerr libcurl_ioctl(CURL*, curliocmd cmd, HTTP_OP* phop) { // reset input stream to beginning - resends header // and restarts data back to starting point switch(cmd) { case CURLIOCMD_RESTARTREAD: phop->lSeek = 0; phop->bytes_xferred = phop->file_offset; phop->bSentHeader = false; break; default: // should never get here return CURLIOE_UNKNOWNCMD; } return CURLIOE_OK; } void libcurl_logdebug( HTTP_OP* phop, const char* desc, char *data, size_t size ) { char hdr[256]; char buf[2048], *p = buf; size_t copy_size; sprintf(hdr, "[ID#%d] %s", phop->trace_id, desc); copy_size = min(size, sizeof(buf)-1); strncpy(buf, data, copy_size); buf[copy_size]='\0'; p = strtok(buf, "\n"); while(p) { if (log_flags.http_debug) { msg_printf(phop->project, MSG_INFO, "[http] %s %s\n", hdr, p ); } p = strtok(NULL, "\n"); } } int libcurl_debugfunction( CURL*, curl_infotype type, char *data, size_t size, HTTP_OP* phop ) { const char* desc = NULL; switch (type) { case CURLINFO_TEXT: desc = "Info: "; break; case CURLINFO_HEADER_OUT: desc = "Sent header to server:"; break; case CURLINFO_HEADER_IN: desc = "Received header from server:"; break; default: /* in case a new one is introduced to shock us */ return 0; } libcurl_logdebug(phop, desc, data, size); return 0; } void HTTP_OP::init(PROJECT* p) { reset(); start_time = gstate.now; start_bytes_xferred = 0; project = p; } void HTTP_OP::reset() { req1 = NULL; strcpy(infile, ""); strcpy(outfile, ""); strcpy(error_msg, ""); CurlResult = CURLE_OK; bTempOutfile = true; want_download = false; want_upload = false; fileIn = NULL; fileOut = NULL; connect_error = 0; bytes_xferred = 0; bSentHeader = false; project = 0; close_socket(); } HTTP_OP::HTTP_OP() { strcpy(m_url, ""); strcpy(m_curl_ca_bundle_location, ""); content_length = 0; file_offset = 0; strcpy(request_header, ""); http_op_state = HTTP_STATE_IDLE; http_op_type = HTTP_OP_NONE; http_op_retval = 0; trace_id = g_trace_count++; pcurlList = NULL; // these have to be NULL, just in constructor curlEasy = NULL; pcurlFormStart = NULL; pcurlFormEnd = NULL; pByte = NULL; lSeek = 0; xfer_speed = 0; is_background = false; reset(); } HTTP_OP::~HTTP_OP() { close_socket(); close_file(); } // Initialize HTTP GET operation; // output goes to the given file, starting at given offset // int HTTP_OP::init_get( PROJECT* p, const char* url, const char* out, bool del_old_file, double off ) { if (del_old_file) { unlink(out); } req1 = NULL; // not using req1, but init_post2 uses it file_offset = off; HTTP_OP::init(p); // usually have an outfile on a get if (off != 0) { bytes_xferred = off; start_bytes_xferred = off; } http_op_type = HTTP_OP_GET; http_op_state = HTTP_STATE_CONNECTING; if (log_flags.http_debug) { msg_printf(project, MSG_INFO, "[http] HTTP_OP::init_get(): %s", url); } return HTTP_OP::libcurl_exec(url, NULL, out, off, false); } // Initialize HTTP POST operation where // the input is a file, and the output is a file, // and both are read/written from the beginning (no resumption of partial ops) // This is used for scheduler requests and account mgr RPCs. // int HTTP_OP::init_post( PROJECT* p, const char* url, const char* in, const char* out ) { int retval; double size; req1 = NULL; // not using req1, but init_post2 uses it if (in) { strcpy(infile, in); retval = file_size(infile, size); if (retval) return retval; // this will return 0 or ERR_NOT_FOUND content_length = (int)size; } HTTP_OP::init(p); http_op_type = HTTP_OP_POST; http_op_state = HTTP_STATE_CONNECTING; if (log_flags.http_debug) { msg_printf(project, MSG_INFO, "[http] HTTP_OP::init_post(): %s", url); } return HTTP_OP::libcurl_exec(url, in, out, 0.0, true); } // Initialize an HTTP POST operation, // where the input is a memory string (r1) followed by an optional file (in) // with optional offset, // and the output goes to memory (also r1, limited by r1_len) // This is used for file upload (both get_file_size and file_upload) // int HTTP_OP::init_post2( PROJECT* p, const char* url, char* r1, int r1_len, const char* in, double offset ) { int retval; double size; init(p); req1 = r1; req1_len = r1_len; content_length = 0; if (in) { safe_strcpy(infile, in); file_offset = offset; retval = file_size(infile, size); if (retval) { printf("HTTP::init_post2: couldn't get file size\n"); return retval; // this will be 0 or ERR_NOT_FOUND } content_length = (int)size - (int)offset; } content_length += (int)strlen(req1); http_op_type = HTTP_OP_POST2; http_op_state = HTTP_STATE_CONNECTING; return HTTP_OP::libcurl_exec(url, in, NULL, offset, true); } // is URL in proxy exception list? // bool HTTP_OP::no_proxy_for_url(const char* url) { PARSED_URL purl, purl2; char noproxy[256]; if (log_flags.proxy_debug) { msg_printf(0, MSG_INFO, "[proxy] HTTP_OP::no_proxy_for_url(): %s", url); } parse_url(url, purl); // tokenize the noproxy-entry and check for identical hosts // strcpy(noproxy, working_proxy_info.noproxy_hosts); char* token = strtok(noproxy, ","); while (token != NULL) { // extract the host from the no_proxy url parse_url(token, purl2); if (!strcmp(purl.host, purl2.host)) { if (log_flags.proxy_debug) { msg_printf(0, MSG_INFO, "[proxy] disabling proxy for %s", url); } return true; } token = strtok(NULL, ","); } if (log_flags.proxy_debug) { msg_printf(0, MSG_INFO, "[proxy] returning false"); } return false; } // the following will do an HTTP GET or POST using libcurl // int HTTP_OP::libcurl_exec( const char* url, const char* in, const char* out, double offset, bool bPost ) { CURLMcode curlMErr; char strTmp[128]; static int outfile_seqno=0; safe_strcpy(m_url, url); if (g_user_agent_string[0] == 0x00) { get_user_agent_string(); } if (in) { strcpy(infile, in); } if (out) { bTempOutfile = false; strcpy(outfile, out); } else { // always want an outfile for the server response, delete when op done bTempOutfile = true; sprintf(outfile, "http_temp_%d", outfile_seqno++); } curlEasy = curl_easy_init(); // get a curl_easy handle to use if (!curlEasy) { if (log_flags.http_debug) { msg_printf(project, MSG_INFO, "Couldn't create curlEasy handle"); } return ERR_HTTP_TRANSIENT; // returns 0 (CURLM_OK) on successful handle creation } // the following seems to be a no-op // curl_easy_setopt(curlEasy, CURLOPT_ERRORBUFFER, error_msg); char esc_url[1024]; string_substitute(m_url, esc_url, sizeof(esc_url), " ", "%20"); curl_easy_setopt(curlEasy, CURLOPT_URL, esc_url); // This option determines whether curl verifies that the server // claims to be who you want it to be. // When negotiating an SSL connection, // the server sends a certificate indicating its identity. // When CURLOPT_SSL_VERIFYHOST is 2, // that certificate must indicate that the server is the server // to which you meant to connect, or the connection fails. // Curl considers the server the intended one when the // Common Name field or a Subject Alternate Name field in the certificate // matches the host name in the URL to which you told Curl to connect. // When the value is 1, the certificate must contain a Common Name field, // but it doesn't matter what name it says. // (This is not ordinarily a useful setting). // When the value is 0, the connection succeeds // regardless of the names in the certificate. // The default, since 7.10, is 2. // The checking this option controls is of the identity that // the server claims. The server could be lying. // To control lying, see CURLOPT_SSL_VERIFYPEER. // curl_easy_setopt(curlEasy, CURLOPT_SSL_VERIFYHOST, 2L); // the following sets "tough" certificate checking // (i.e. whether self-signed is OK) // if zero below, will accept self-signed certificates // (cert not 3rd party trusted) // if non-zero below, you need a valid 3rd party CA (i.e. Verisign, Thawte) // curl_easy_setopt(curlEasy, CURLOPT_SSL_VERIFYPEER, 1L); // if the above is nonzero, you need the following: // #ifdef _WIN32 if (strlen(m_curl_ca_bundle_location) == 0) { TCHAR szPath[MAX_PATH-1]; GetModuleFileName(NULL, szPath, (sizeof(szPath)/sizeof(TCHAR))); TCHAR *pszProg = strrchr(szPath, '\\'); if (pszProg) { szPath[pszProg - szPath + 1] = 0; strncat( m_curl_ca_bundle_location, szPath, sizeof(m_curl_ca_bundle_location)-strlen(m_curl_ca_bundle_location) ); strncat( m_curl_ca_bundle_location, CA_BUNDLE_FILENAME, sizeof(m_curl_ca_bundle_location)-strlen(m_curl_ca_bundle_location) ); if (log_flags.http_debug) { msg_printf( project, MSG_INFO, "[http] HTTP_OP::libcurl_exec(): ca-bundle '%s'", m_curl_ca_bundle_location ); } } } if (boinc_file_exists(m_curl_ca_bundle_location)) { // call this only if a local copy of ca-bundle.crt exists; // otherwise, let's hope that it exists in the default place // curl_easy_setopt(curlEasy, CURLOPT_CAINFO, m_curl_ca_bundle_location); if (log_flags.http_debug) { msg_printf( project, MSG_INFO, "[http] HTTP_OP::libcurl_exec(): ca-bundle set" ); } } #else if (boinc_file_exists(CA_BUNDLE_FILENAME)) { // call this only if a local copy of ca-bundle.crt exists; // otherwise, let's hope that it exists in the default place // curl_easy_setopt(curlEasy, CURLOPT_CAINFO, CA_BUNDLE_FILENAME); } #endif // set the user agent as this boinc client & version // curl_easy_setopt(curlEasy, CURLOPT_USERAGENT, g_user_agent_string); // bypass any signal handlers that curl may want to install // curl_easy_setopt(curlEasy, CURLOPT_NOSIGNAL, 1L); // bypass progress meter // curl_easy_setopt(curlEasy, CURLOPT_NOPROGRESS, 1L); // setup timeouts // curl_easy_setopt(curlEasy, CURLOPT_TIMEOUT, 0L); curl_easy_setopt(curlEasy, CURLOPT_LOW_SPEED_LIMIT, config.http_transfer_timeout_bps); curl_easy_setopt(curlEasy, CURLOPT_LOW_SPEED_TIME, config.http_transfer_timeout); curl_easy_setopt(curlEasy, CURLOPT_CONNECTTIMEOUT, 120L); // force curl to use HTTP/1.0 if config specifies it // (curl uses 1.1 by default) // if (config.http_1_0 || (config.force_auth == "ntlm")) { curl_easy_setopt(curlEasy, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); } curl_easy_setopt(curlEasy, CURLOPT_MAXREDIRS, 50L); curl_easy_setopt(curlEasy, CURLOPT_AUTOREFERER, 1L); curl_easy_setopt(curlEasy, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curlEasy, CURLOPT_POST301, 1L); // if we tell Curl to accept any encoding (e.g. deflate) // it seems to accept them all, which screws up projects that // use gzip at the application level. // So, detect this and don't accept any encoding in that case // // Per: http://curl.haxx.se/dev/readme-encoding.html // NULL disables, empty string accepts all. if (out) { if (ends_with(out, ".gzt")) { curl_easy_setopt(curlEasy, CURLOPT_ENCODING, NULL); } else if (!ends_with(out, ".gz")) { curl_easy_setopt(curlEasy, CURLOPT_ENCODING, ""); } } else { curl_easy_setopt(curlEasy, CURLOPT_ENCODING, ""); } // setup any proxy they may need // setup_proxy_session(no_proxy_for_url(url)); // set the content type in the header // pcurlList = curl_slist_append(pcurlList, g_content_type); // set the file offset for resumable downloads // if (!bPost && offset>0.0f) { file_offset = offset; sprintf(strTmp, "Range: bytes=%.0f-", offset); pcurlList = curl_slist_append(pcurlList, strTmp); } // set up an output file for the reply // if (strlen(outfile)) { if (file_offset>0.0) { fileOut = boinc_fopen(outfile, "ab+"); } else { fileOut = boinc_fopen(outfile, "wb+"); } if (!fileOut) { msg_printf(NULL, MSG_INTERNAL_ERROR, "Can't create HTTP response output file %s", outfile ); http_op_retval = ERR_FOPEN; http_op_state = HTTP_STATE_DONE; return ERR_FOPEN; } // we can make the libcurl_write "fancier" in the future, // for now it just fwrite's to the file request, which is sufficient // curl_easy_setopt(curlEasy, CURLOPT_WRITEFUNCTION, libcurl_write); // note that in my lib_write I'm sending in a pointer // to this instance of HTTP_OP // curl_easy_setopt(curlEasy, CURLOPT_WRITEDATA, this); } if (bPost) { want_upload = true; want_download = false; if (infile && strlen(infile)>0) { fileIn = boinc_fopen(infile, "rb"); if (!fileIn) { msg_printf(NULL, MSG_INTERNAL_ERROR, "No HTTP input file %s", infile); http_op_retval = ERR_FOPEN; http_op_state = HTTP_STATE_DONE; return ERR_FOPEN; } } if (pcurlList) { // send custom headers if required curl_easy_setopt(curlEasy, CURLOPT_HTTPHEADER, pcurlList); } // set the data file info to read for the PUT/POST // note the use of this curl typedef for large filesizes #if 0 // HTTP PUT method curl_off_t fs = (curl_off_t) content_length; curl_easy_setopt(curlEasy, CURLOPT_POSTFIELDS, NULL); curl_easy_setopt(curlEasy, CURLOPT_INFILESIZE, content_length); curl_easy_setopt(curlEasy, CURLOPT_READDATA, fileIn); curl_easy_setopt(curlEasy, CURLOPT_INFILESIZE_LARGE, fs); curl_easy_setopt(curlEasy, CURLOPT_PUT, 1L); #endif // HTTP POST method // set the multipart form for the file -- // boinc just has the one section (file) #if 0 // if we ever want to do POST as multipart forms someday // (many seem to prefer it that way, i.e. libcurl) // pcurlFormStart = pcurlFormEnd = NULL; curl_formadd(&pcurlFormStart, &pcurlFormEnd, CURLFORM_FILECONTENT, infile, CURLFORM_CONTENTSLENGTH, content_length, CURLFORM_CONTENTTYPE, g_content_type, CURLFORM_END ); curl_formadd(&post, &last, CURLFORM_COPYNAME, "logotype-image", CURLFORM_FILECONTENT, "curl.png", CURLFORM_END ); curl_easy_setopt(curlEasy, CURLOPT_HTTPPOST, pcurlFormStart); #endif curl_off_t fs = (curl_off_t) content_length; pByte = NULL; lSeek = 0; // initialize the vars we're going to use for byte transfers // we can make the libcurl_read "fancier" in the future, // for now it just fwrite's to the file request, which is sufficient // curl_easy_setopt(curlEasy, CURLOPT_POSTFIELDS, NULL); curl_easy_setopt(curlEasy, CURLOPT_POSTFIELDSIZE_LARGE, fs); curl_easy_setopt(curlEasy, CURLOPT_READFUNCTION, libcurl_read); // in my lib_write I'm sending in a pointer to this instance of HTTP_OP // curl_easy_setopt(curlEasy, CURLOPT_READDATA, this); // callback function to rewind input file // curl_easy_setopt(curlEasy, CURLOPT_IOCTLFUNCTION, libcurl_ioctl); curl_easy_setopt(curlEasy, CURLOPT_IOCTLDATA, this); curl_easy_setopt(curlEasy, CURLOPT_POST, 1L); } else { // GET want_upload = false; want_download = true; // now write the header, pcurlList gets freed in net_xfer_curl // if (pcurlList) { // send custom headers if required curl_easy_setopt(curlEasy, CURLOPT_HTTPHEADER, pcurlList); } // setup the GET! // curl_easy_setopt(curlEasy, CURLOPT_HTTPGET, 1L); } #ifdef __APPLE__ // cURL 7.19.7 with c-ares 1.7.0 did not fall back to IPv4 when IPv6 // DNS lookup failed on Macs with certain default settings if connected // to the Internet by an AT&T U-Verse 2-Wire Gateway. This work-around // may not be needed any more for cURL 7.21.7, but keep it to be safe. curl_easy_setopt(curlEasy, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); #endif // turn on debug info if tracing enabled // if (log_flags.http_debug) { curl_easy_setopt(curlEasy, CURLOPT_DEBUGFUNCTION, libcurl_debugfunction); curl_easy_setopt(curlEasy, CURLOPT_DEBUGDATA, this ); curl_easy_setopt(curlEasy, CURLOPT_VERBOSE, 1L); } // last but not least, add this to the curl_multi curlMErr = curl_multi_add_handle(g_curlMulti, curlEasy); if (curlMErr != CURLM_OK && curlMErr != CURLM_CALL_MULTI_PERFORM) { // bad error, couldn't attach easy curl handle msg_printf(0, MSG_INTERNAL_ERROR, "Couldn't add curlEasy handle to curlMulti" ); return ERR_HTTP_TRANSIENT; // returns 0 (CURLM_OK) on successful handle creation } return 0; } // Returns true if the HTTP operation is complete // bool HTTP_OP::http_op_done() { return (http_op_state == HTTP_STATE_DONE); } HTTP_OP_SET::HTTP_OP_SET() { bytes_up = 0; bytes_down = 0; } // Adds an HTTP_OP to the set // void HTTP_OP_SET::insert(HTTP_OP* ho) { http_ops.push_back(ho); } // Remove an HTTP_OP from the set // int HTTP_OP_SET::remove(HTTP_OP* p) { vector::iterator iter; iter = http_ops.begin(); while (iter != http_ops.end()) { if (*iter == p) { iter = http_ops.erase(iter); return 0; } iter++; } return ERR_NOT_FOUND; } int HTTP_OP_SET::nops() { return (int)http_ops.size(); } // Curl self-explanatory setopt params for proxies: // CURLOPT_HTTPPROXYTUNNEL // CURLOPT_PROXYTYPE (pass in CURLPROXY_HTTP or CURLPROXY_SOCKS5) // CURLOPT_PROXYPORT -- a long port # // CURLOPT_PROXY - pass in char* of the proxy url // CURLOPT_PROXYUSERPWD -- a char* in the format username:password // CURLOPT_HTTPAUTH -- pass in one of CURLAUTH_BASIC, CURLAUTH_DIGEST, // CURLAUTH_GSSNEGOTIATE, CURLAUTH_NTLM, CURLAUTH_ANY, CURLAUTH_ANYSAFE // CURLOPT_PROXYAUTH -- "or" | the above bitmasks -- only basic, digest, ntlm work void HTTP_OP::setup_proxy_session(bool no_proxy) { // CMC Note: the string m_curl_user_credentials must remain in memory // outside of this method (libcurl relies on it later when it makes // the proxy connection), so it has been placed as a member data for HTTP_OP // strcpy(m_curl_user_credentials, ""); if (no_proxy) { curl_easy_setopt(curlEasy, CURLOPT_PROXY, ""); return; } pi = working_proxy_info; if (pi.use_http_proxy) { if (log_flags.proxy_debug) { msg_printf( 0, MSG_INFO, "[proxy]: setting up proxy %s:%d", pi.http_server_name, pi.http_server_port ); } // setup a basic http proxy curl_easy_setopt(curlEasy, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); curl_easy_setopt(curlEasy, CURLOPT_PROXYPORT, (long) pi.http_server_port); curl_easy_setopt(curlEasy, CURLOPT_PROXY, (char*) pi.http_server_name); if (pi.use_http_auth) { if (config.force_auth == "basic") { curl_easy_setopt(curlEasy, CURLOPT_PROXYAUTH, CURLAUTH_BASIC); } else if (config.force_auth == "digest") { curl_easy_setopt(curlEasy, CURLOPT_PROXYAUTH, CURLAUTH_DIGEST); } else if (config.force_auth == "gss-negotiate") { curl_easy_setopt(curlEasy, CURLOPT_PROXYAUTH, CURLAUTH_GSSNEGOTIATE); } else if (config.force_auth == "ntlm") { curl_easy_setopt(curlEasy, CURLOPT_PROXYAUTH, CURLAUTH_NTLM); } else { curl_easy_setopt(curlEasy, CURLOPT_PROXYAUTH, CURLAUTH_ANY); } sprintf(m_curl_user_credentials, "%s:%s", pi.http_user_name, pi.http_user_passwd); curl_easy_setopt(curlEasy, CURLOPT_PROXYUSERPWD, m_curl_user_credentials); } } else if (pi.use_socks_proxy) { // CURL only supports SOCKS version 5 curl_easy_setopt(curlEasy, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5); curl_easy_setopt(curlEasy, CURLOPT_PROXYPORT, (long) pi.socks_server_port); curl_easy_setopt(curlEasy, CURLOPT_PROXY, (char*) pi.socks_server_name); // libcurl uses blocking sockets with socks proxy, so limit timeout. // - imlemented with local patch to libcurl curl_easy_setopt(curlEasy, CURLOPT_CONNECTTIMEOUT, 20L); if ( strlen(pi.socks5_user_passwd) || strlen(pi.socks5_user_name) ) { sprintf(m_curl_user_credentials, "%s:%s", pi.socks5_user_name, pi.socks5_user_passwd); curl_easy_setopt(curlEasy, CURLOPT_PROXYUSERPWD, m_curl_user_credentials); curl_easy_setopt(curlEasy, CURLOPT_PROXYAUTH, CURLAUTH_ANY & ~CURLAUTH_NTLM); } } else if (pi.have_autodetect_proxy_settings && strlen(pi.autodetect_server_name)) { if (log_flags.proxy_debug) { msg_printf(0, MSG_INFO, "[proxy] HTTP_OP::setup_proxy_session(): setting up automatic proxy %s:%d", pi.autodetect_server_name, pi.autodetect_port ); } switch(pi.autodetect_protocol) { case URL_PROTOCOL_SOCKS: curl_easy_setopt(curlEasy, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5); break; case URL_PROTOCOL_HTTP: case URL_PROTOCOL_HTTPS: default: curl_easy_setopt(curlEasy, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); break; } curl_easy_setopt(curlEasy, CURLOPT_PROXYPORT, (long) pi.autodetect_port); curl_easy_setopt(curlEasy, CURLOPT_PROXY, (char*) pi.autodetect_server_name); } } // the file descriptor sets need to be global so libcurl has access always // fd_set read_fds, write_fds, error_fds; // call these once at the start of the program and once at the end // int curl_init() { curl_global_init(CURL_GLOBAL_ALL); g_curlMulti = curl_multi_init(); return (int)(g_curlMulti == NULL); } int curl_cleanup() { if (g_curlMulti) { curl_multi_cleanup(g_curlMulti); } curl_global_cleanup(); return 0; } void HTTP_OP::close_socket() { // this cleans up the curlEasy, and "spoofs" the old close_socket // if (pcurlList) { curl_slist_free_all(pcurlList); pcurlList = NULL; } if (curlEasy && pcurlFormStart) { curl_formfree(pcurlFormStart); curl_formfree(pcurlFormEnd); pcurlFormStart = pcurlFormEnd = NULL; } if (curlEasy && g_curlMulti) { // release this handle curl_multi_remove_handle(g_curlMulti, curlEasy); curl_easy_cleanup(curlEasy); curlEasy = NULL; } } void HTTP_OP::close_file() { if (fileIn) { fclose(fileIn); fileIn = NULL; } if (fileOut) { fclose(fileOut); fileOut = NULL; } if (pByte) { //free any read memory used delete [] pByte; pByte = NULL; } } void HTTP_OP_SET::get_fdset(FDSET_GROUP& fg) { curl_multi_fdset( g_curlMulti, &fg.read_fds, &fg.write_fds, &fg.exc_fds, &fg.max_fd ); } // we have a message for this HTTP_OP. // get the response code for this request // void HTTP_OP::handle_messages(CURLMsg *pcurlMsg) { int retval; curl_easy_getinfo(curlEasy, CURLINFO_RESPONSE_CODE, &response ); curl_easy_getinfo(curlEasy, CURLINFO_OS_ERRNO, &connect_error ); // update byte counts and transfer speed // if (want_download) { // SIZE_DOWNLOAD is the byte count "on the wire" // (possible with compression) // TOTAL_TIME is the elapsed time of the download // STARTTRANSFER_TIME is portion of elapsed time involved // with setup (connection establishment etc.) // SPEED_DOWNLOAD is bytes/sec based on uncompressed size // (we don't use it) // double size_download, total_time, starttransfer_time; curl_easy_getinfo(curlEasy, CURLINFO_SIZE_DOWNLOAD, &size_download); curl_easy_getinfo(curlEasy, CURLINFO_TOTAL_TIME, &total_time); curl_easy_getinfo(curlEasy, CURLINFO_STARTTRANSFER_TIME, &starttransfer_time ); double dt = total_time - starttransfer_time; if (dt > 0) { gstate.net_stats.down.update(size_download, dt); } } if (want_upload) { double size_upload, total_time, starttransfer_time; curl_easy_getinfo(curlEasy, CURLINFO_SIZE_UPLOAD, &size_upload); curl_easy_getinfo(curlEasy, CURLINFO_TOTAL_TIME, &total_time); curl_easy_getinfo(curlEasy, CURLINFO_STARTTRANSFER_TIME, &starttransfer_time ); double dt = total_time - starttransfer_time; if (dt > 0) { gstate.net_stats.up.update(size_upload, dt); } } // the op is done if curl_multi_msg_read gave us a msg for this http_op // http_op_state = HTTP_STATE_DONE; CurlResult = pcurlMsg->data.result; if (CurlResult == CURLE_OK) { if ((response/100)*100 == HTTP_STATUS_OK) { http_op_retval = 0; } else if ((response/100)*100 == HTTP_STATUS_CONTINUE) { return; } else { // Got a response from server but its not OK or CONTINUE, // so save response with error message to display later. // if (response >= 400) { strcpy(error_msg, boincerror(response)); } else { sprintf(error_msg, "HTTP error %ld", response); } switch (response) { case HTTP_STATUS_NOT_FOUND: case HTTP_STATUS_RANGE_REQUEST_ERROR: http_op_retval = ERR_HTTP_PERMANENT; break; default: http_op_retval = ERR_HTTP_TRANSIENT; } } net_status.http_op_succeeded(); } else { strcpy(error_msg, curl_easy_strerror(CurlResult)); switch(CurlResult) { case CURLE_COULDNT_RESOLVE_HOST: reset_dns(); http_op_retval = ERR_GETHOSTBYNAME; break; case CURLE_COULDNT_CONNECT: http_op_retval = ERR_CONNECT; break; default: http_op_retval = ERR_HTTP_TRANSIENT; } // trigger a check for whether we're connected, // but not if this is a background operation // if (!is_background) { net_status.got_http_error(); } if (log_flags.http_debug) { msg_printf(project, MSG_INFO, "[http] HTTP error: %s", error_msg ); } } if (!http_op_retval && http_op_type == HTTP_OP_POST2) { // for a successfully completed request on a "post2" -- // read in the temp file into req1 memory // size_t dSize = ftell(fileOut); retval = fseek(fileOut, 0, SEEK_SET); if (retval) { // flag as a bad response for a possible retry later response = 1; msg_printf(NULL, MSG_INTERNAL_ERROR, "[http] can't rewind post output file %s", outfile ); } else { strcpy(req1, ""); if (dSize >= (size_t)req1_len) { dSize = req1_len-1; } size_t nread = fread(req1, 1, dSize, fileOut); if (nread != dSize) { if (log_flags.http_debug) { msg_printf(project, MSG_INFO, "[http] post output file read failed %d", (int)nread ); } } req1[nread] = 0; } } // close files and "sockets" (i.e. libcurl handles) // close_file(); close_socket(); // finally remove the tmpfile if not explicitly set // if (bTempOutfile) { boinc_delete_file(outfile); } } void HTTP_OP_SET::got_select(FDSET_GROUP&, double timeout) { int iNumMsg; HTTP_OP* hop = NULL; CURLMsg *pcurlMsg = NULL; int iRunning = 0; // curl flags for max # of fds & # running queries CURLMcode curlMErr; // get the data waiting for transfer in or out // use timeout value so that we don't hog CPU in this loop // while (1) { curlMErr = curl_multi_perform(g_curlMulti, &iRunning); if (curlMErr != CURLM_CALL_MULTI_PERFORM) break; if (dtime() - gstate.now > timeout) break; } // read messages from curl that may have come in from the above loop // while (1) { pcurlMsg = curl_multi_info_read(g_curlMulti, &iNumMsg); if (!pcurlMsg) break; // if we have a msg, then somebody finished // can check also with pcurlMsg->msg == CURLMSG_DONE // hop = lookup_curl(pcurlMsg->easy_handle); if (!hop) continue; hop->handle_messages(pcurlMsg); } } // Return the HTTP_OP object with given Curl object // HTTP_OP* HTTP_OP_SET::lookup_curl(CURL* pcurl) { for (unsigned int i=0; icurlEasy == pcurl) { return http_ops[i]; } } return 0; } // Update the transfer speed for this HTTP_OP // called on every I/O // void HTTP_OP::update_speed() { double delta_t = dtime() - start_time; if (delta_t > 0) { xfer_speed = (bytes_xferred-start_bytes_xferred) / delta_t; } } void HTTP_OP::set_speed_limit(bool is_upload, double bytes_sec) { #if LIBCURL_VERSION_NUM >= 0x070f05 CURLcode cc = CURLE_OK; curl_off_t bs = (curl_off_t)bytes_sec; if (is_upload) { cc = curl_easy_setopt(curlEasy, CURLOPT_MAX_SEND_SPEED_LARGE, bs); } else { cc = curl_easy_setopt(curlEasy, CURLOPT_MAX_RECV_SPEED_LARGE, bs); } if (cc && log_flags.http_debug) { msg_printf(project, MSG_INFO, "[http] Curl error in set_speed_limit(): %s", curl_easy_strerror(cc) ); } #endif } void HTTP_OP_SET::cleanup_temp_files() { char filename[256]; DIRREF d = dir_open("."); while (1) { int retval = dir_scan(filename, d, sizeof(filename)); if (retval) break; if (strstr(filename, "blc") != filename) continue; if (!is_file(filename)) continue; boinc_delete_file(filename); } dir_close(d); }