// Berkeley Open Infrastructure for Network Computing // http://boinc.berkeley.edu // Copyright (C) 2005 University of California // // This 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 2.1 of the License, or (at your option) any later version. // // This software 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. // // To view the GNU Lesser General Public License visit // http://www.gnu.org/copyleft/lesser.html // or write to the Free Software Foundation, Inc., // 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "cpp.h" #ifdef _WIN32 #include "boinc_win.h" #else #include "config.h" #include #include #include #include #include #include #endif #include "error_numbers.h" #include "filesys.h" #include "util.h" //#include "network.h" #include "client_msgs.h" #include "base64.h" #include "http_curl.h" #define CONNECTED_STATE_NOT_CONNECTED 0 #define CONNECTED_STATE_CONNECTED 1 #define CONNECTED_STATE_UNKNOWN 2 #define HTTP_BLOCKSIZE 16384 using std::string; using std::istringstream; using std::vector; using std::getline; extern CURLM* g_curlMulti; // global curl multi handle for http/s static char g_user_agent_string[256] = {""}; //static const char g_content_type[] = {"Content-Type: application/octet-stream"}; // CMC Note: old BOINC used the above, but libcurl seems to like the following: static const char g_content_type[] = {"Content-Type: application/x-www-form-urlencoded"}; /** as an example of usage of http_op -- the scheduler typically requires just the following: http_op http_op_state flag http_op_retval set_proxy (g_state.proxy_info) init_get() init_post() http_ops -> insert remove */ // Breaks a HTTP URL down into its server, port and file components // format of url: // [http[s]://]host.dom.dom[:port][/dir/file] // void parse_url(const char* url, char* host, int &port, char* file) { char* p; char buf[256]; bool bSSL = false; //const short csiLen = bSSL ? 8 : 7; // get right size of http/s string comparator // strip off http:// if present // //if (strncmp(url, (bSSL ? "https://" : "http://"), csiLen) == 0) { if (strncmp(url, "http://", 7) == 0) { safe_strcpy(buf, url+7); } else { // wait, may be https:// if (strncmp(url, "https://", 8) == 0) { safe_strcpy(buf, url+8); bSSL = true; // retain the fact that this was a secure http url } else { // no http:// or https:// prepended on url safe_strcpy(buf, url); } } // parse and strip off file part if present // p = strchr(buf, '/'); if (p) { strcpy(file, p+1); *p = 0; } else { strcpy(file, ""); } // parse and strip off port if present // p = strchr(buf,':'); if (p) { port = atol(p+1); *p = 0; } else { // CMC note: if they didn't pass in a port #, // but the url starts with https://, assume they // want a secure port (HTTPS, port 443) port = (bSSL ? 443 : 80); } // what remains is the host // strcpy(host, buf); } void get_user_agent_string() { sprintf(g_user_agent_string, "BOINC client (%s %d.%02d)", HOSTTYPE, BOINC_MAJOR_VERSION, BOINC_MINOR_VERSION ); } HTTP_OP::HTTP_OP() { strcpy(m_url, ""); // CMC added this to "preserve" the url for libcurl strcpy(url_hostname, ""); strcpy(filename, ""); 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; } HTTP_OP::~HTTP_OP() { } // Initialize HTTP GET operation // int HTTP_OP::init_get( const char* url, const char* out, bool del_old_file, double off ) { //char proxy_buf[256]; if (del_old_file) { unlink(out); } req1 = NULL; // not using req1, but init_post2 uses it file_offset = off; safe_strcpy(m_url, url); parse_url(url, url_hostname, port, filename); NET_XFER::init(url_hostname, port, HTTP_BLOCKSIZE); // usually have an outfile on a get if (off != 0) { bytes_xferred = off; } http_op_type = HTTP_OP_GET; http_op_state = HTTP_STATE_CONNECTING; return HTTP_OP::libcurl_exec(url, NULL, out, off, false); } // Initialize HTTP POST operation // int HTTP_OP::init_post( const char* url, const char* in, const char* out ) { int retval; double size; SCOPE_MSG_LOG scope_messages(log_messages, CLIENT_MSG_LOG::DEBUG_HTTP); req1 = NULL; // not using req1, but init_post2 uses it strcpy(m_url, url); parse_url(url, url_hostname, port, filename); if (in) { // we should pretty much always have an in file for _post, optional in _post2 strcpy(infile, in); retval = file_size(infile, size); if (retval) return retval; // this will return 0 or ERR_NOT_FOUND content_length = (int)size; } //PROXY::init(url_hostname, port); NET_XFER::init(url_hostname,port, HTTP_BLOCKSIZE); http_op_type = HTTP_OP_POST; http_op_state = HTTP_STATE_CONNECTING; scope_messages.printf("HTTP_OP::init_post(): %p io_done %d\n", this, io_done); return HTTP_OP::libcurl_exec(url, in, out, 0.0, true); // note that no offset for this, for resumable uploads use post2! } // the following will do an HTTP GET or POST using libcurl, // polling at the net_xfer level // int HTTP_OP::libcurl_exec( const char* , const char* in, const char* out, double offset, bool bPost ) { CURLMcode curlMErr; CURLcode curlErr; //char proxy_buf[256]; char strTmp[128]; // get user agent string if (g_user_agent_string[0] == 0x00) { get_user_agent_string(); } if (in) { strcpy(infile, in); } if (out) { bTempOutfile = false; strcpy(outfile, out); } else { //CMC -- I always want an outfile for the server response, delete when op done bTempOutfile = true; memset(outfile, 0x00, _MAX_PATH); #ifdef _WIN32 char* ptrName; ptrName = _tempnam("./", "blc"); if (ptrName) { strcpy(outfile, ptrName); free(ptrName); } #else // use mkstemp on Mac & Linux due to security issues strcpy(outfile, "blcXXXXXX"); // a template for the mkstemp close(mkstemp(outfile)); #endif } // setup libcurl handle SCOPE_MSG_LOG scope_messages(log_messages, CLIENT_MSG_LOG::DEBUG_NET_XFER); // CMC -- init the curlEasy handle and setup options // the polling will do the actual start of the HTTP/S transaction curlEasy = curl_easy_init(); // get a curl_easy handle to use if (!curlEasy) { msg_printf(0, MSG_ERROR, "%s\n", "Couldn't create curlEasy handle\n"); return ERR_HTTP_ERROR; // returns 0 (CURLM_OK) on successful handle creation } // OK, we have a handle, now open an asynchronous libcurl connection // set the URL to use curlErr = curl_easy_setopt(curlEasy, CURLOPT_URL, m_url); /* CURLOPT_SSL_VERIFYHOST Pass a long as parameter. 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. */ curlErr = curl_easy_setopt(curlEasy, CURLOPT_SSL_VERIFYHOST, 2L); // disable "tough" certificate checking (i.e. 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) curlErr = curl_easy_setopt(curlEasy, CURLOPT_SSL_VERIFYPEER, 0L); // set the user agent as this boinc client & version curlErr = curl_easy_setopt(curlEasy, CURLOPT_USERAGENT, g_user_agent_string); // note: custom headers here means EVERYTHING needs to be set here, // including form content types. Probably best just to set the user agent above // and let libcurl handle the headers. // create custom header for BOINC #if 0 pcurlList = curl_slist_append(pcurlList, "Pragma: no-cache"); pcurlList = curl_slist_append(pcurlList, "Cache-Control: no-cache"); pcurlList = curl_slist_append(pcurlList, g_user_agent_string); //sprintf(strTmp, "Host: %s:%d", url_hostname, port); //pcurlList = curl_slist_append(pcurlList, strTmp); if (offset>0.0f) { file_offset = offset; sprintf(strTmp, "Range: bytes=%.0f-", offset); pcurlList = curl_slist_append(pcurlList, strTmp); } pcurlList = curl_slist_append(pcurlList, "Connection: close"); pcurlList = curl_slist_append(pcurlList, "Accept: *\/*"); // "Proxy-Authorization: Basic %s\015\012" if (pcurlList) { // send custom headers if required curlErr = curl_easy_setopt(curlEasy, CURLOPT_HTTPHEADER, pcurlList); } #endif // bypass any signal handlers that curl may want to install curlErr = curl_easy_setopt(curlEasy, CURLOPT_NOSIGNAL, 1L); // bypass progress meter curlErr = curl_easy_setopt(curlEasy, CURLOPT_NOPROGRESS, 1L); // setup timeouts curlErr = curl_easy_setopt(curlEasy, CURLOPT_TIMEOUT, 0L); curlErr = curl_easy_setopt(curlEasy, CURLOPT_LOW_SPEED_LIMIT, 10L); curlErr = curl_easy_setopt(curlEasy, CURLOPT_LOW_SPEED_TIME, 300L); curlErr = curl_easy_setopt(curlEasy, CURLOPT_CONNECTTIMEOUT, 120L); // force curl to use HTTP/1.0 (which the old BOINC http libraries did) //curlErr = curl_easy_setopt(curlEasy, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); curlErr = curl_easy_setopt(curlEasy, CURLOPT_MAXREDIRS, 5L); curlErr = curl_easy_setopt(curlEasy, CURLOPT_AUTOREFERER, 1L); curlErr = curl_easy_setopt(curlEasy, CURLOPT_FOLLOWLOCATION, 1L); // setup any proxy they may need setupProxyCurl(); // set the content type in the header (defined at the top -- app/octect-stream) pcurlList = curl_slist_append(pcurlList, g_content_type); // set the file offset for resumable uploads & downloads if (offset>0.0f) { file_offset = offset; sprintf(strTmp, "Range: bytes=%.0f-", offset); pcurlList = curl_slist_append(pcurlList, strTmp); } // we'll need to setup an output file for the reply if (outfile && strlen(outfile)>0) { // if offset>0 don't truncate! char szType[3] = "wb"; if (file_offset>0.0) szType[0] = 'a'; fileOut = boinc_fopen(outfile, szType); if (!fileOut) { msg_printf(NULL, MSG_ERROR, "HTTP_CURL:libcurl_exec(): Can't setup HTTP response output file %s\n", outfile); io_done = true; http_op_retval = ERR_FOPEN; http_op_state = HTTP_STATE_DONE; return ERR_FOPEN; } // CMC Note: we can make the libcurl_write "fancier" in the future, // for now it just fwrite's to the file request, which is sufficient curlErr = 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 curlErr = curl_easy_setopt(curlEasy, CURLOPT_WRITEDATA, this); } if (bPost) { // POST want_upload = true; want_download = false; if (infile && strlen(infile)>0) { fileIn = boinc_fopen(infile, "rb"); if (!fileIn) { msg_printf(NULL, MSG_ERROR, "HTTP_CURL:libcurl_exec(): no input file %s\n", infile); io_done = true; http_op_retval = ERR_FOPEN; http_op_state = HTTP_STATE_DONE; return ERR_FOPEN; } } if (pcurlList) { // send custom headers if required curlErr = 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 /* HTTP PUT method curl_off_t fs = (curl_off_t) content_length; curlErr = curl_easy_setopt(curlEasy, CURLOPT_POSTFIELDS, NULL); curlErr = curl_easy_setopt(curlEasy, CURLOPT_INFILESIZE, content_length); curlErr = curl_easy_setopt(curlEasy, CURLOPT_READDATA, fileIn); curlErr = curl_easy_setopt(curlEasy, CURLOPT_INFILESIZE_LARGE, fs); curlErr = curl_easy_setopt(curlEasy, CURLOPT_PUT, 1L); */ // HTTP POST method // set the multipart form for the file -- boinc just has the one section (file) /* CMC -- 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); */ //curlErr = curl_easy_setopt(curlEasy, CURLOPT_HTTPPOST, pcurlFormStart); curl_off_t fs = (curl_off_t) content_length; pByte = NULL; lSeek = 0; // initialize the vars we're going to use for byte transfers // CMC Note: we can make the libcurl_read "fancier" in the future, // for now it just fwrite's to the file request, which is sufficient curlErr = curl_easy_setopt(curlEasy, CURLOPT_POSTFIELDS, NULL); curlErr = curl_easy_setopt(curlEasy, CURLOPT_POSTFIELDSIZE_LARGE, fs); curlErr = curl_easy_setopt(curlEasy, CURLOPT_READFUNCTION, libcurl_read); // note that in my lib_write I'm sending in a pointer to this instance of HTTP_OP curlErr = curl_easy_setopt(curlEasy, CURLOPT_READDATA, this); curlErr = 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 curlErr = curl_easy_setopt(curlEasy, CURLOPT_HTTPHEADER, pcurlList); } // setup the GET! curlErr = curl_easy_setopt(curlEasy, CURLOPT_HTTPGET, 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_ERROR, "%s\n", "Couldn't add curlEasy handle to curlMulti\n"); return ERR_HTTP_ERROR; // returns 0 (CURLM_OK) on successful handle creation } // that should about do it, the net_xfer_set polling will actually start the transaction // for this HTTP_OP return 0; } // Initialize HTTP POST operation // int HTTP_OP::init_post2( const char* url, char* r1, const char* in, double offset ) { int retval; double size; //char proxy_buf[256]; strcpy(m_url, url); parse_url(url, url_hostname, port, filename); //PROXY::init(url_hostname, port); //NET_XFER::init(get_proxy_server_name(url_hostname),get_proxy_port(port), HTTP_BLOCKSIZE); NET_XFER::init(url_hostname,port, HTTP_BLOCKSIZE); req1 = r1; 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 += strlen(req1); http_op_type = HTTP_OP_POST2; http_op_state = HTTP_STATE_CONNECTING; return HTTP_OP::libcurl_exec(url, in, NULL, offset, true); } // 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(NET_XFER_SET* p) { net_xfers = p; } // Adds an HTTP_OP to the set // int HTTP_OP_SET::insert(HTTP_OP* ho) { int retval; retval = net_xfers->insert(ho); if (retval) return retval; // this will return 0 or a BOINC ERR_* message http_ops.push_back(ho); return 0; } // Remove an HTTP_OP from the set // int HTTP_OP_SET::remove(HTTP_OP* p) { vector::iterator iter; net_xfers->remove(p); iter = http_ops.begin(); while (iter != http_ops.end()) { if (*iter == p) { http_ops.erase(iter); return 0; } iter++; } msg_printf(NULL, MSG_ERROR, "HTTP_OP_SET::remove(): not found\n"); return ERR_NOT_FOUND; } int HTTP_OP_SET::nops() { return http_ops.size(); } int HTTP_OP::set_proxy(PROXY_INFO *new_pi) { pi.use_http_proxy = new_pi->use_http_proxy; strcpy(pi.http_user_name, new_pi->http_user_name); strcpy(pi.http_user_passwd, new_pi->http_user_passwd); strcpy(pi.http_server_name, new_pi->http_server_name); pi.http_server_port = new_pi->http_server_port; pi.use_http_auth = new_pi->use_http_auth; pi.use_socks_proxy = new_pi->use_socks_proxy; strcpy(pi.socks5_user_name, new_pi->socks5_user_name); strcpy(pi.socks5_user_passwd, new_pi->socks5_user_passwd); strcpy(pi.socks_server_name, new_pi->socks_server_name); pi.socks_server_port = new_pi->socks_server_port; pi.socks_version = new_pi->socks_version; return 0; } 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 //CMC TODO: maybe assert stRead == size*nmemb, add exception handling on phop members size_t stWrite = fwrite(ptr, size, nmemb, (FILE*) phop->fileOut); phop->bytes_xferred += (double)(stWrite); //if (phop->bytes_xferred == (int) phop->content_length) //{ // that's all we need! //} phop->update_speed(); // this should update the transfer speed 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->http_op_type == HTTP_OP_POST2) { if (phop->req1 && ! phop->bSentHeader) { // need to send headers first, then data file // uck -- the way 'post2' is done, you have to read the // header bytes, and then cram on the file upload bytes // so requests from 0 to strlen(req1)-1 are from memory, // and from strlen(req1) to content_length are from the file // just send the headers from htp->req1 if needed 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 = 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 phop->bytes_xferred += (double)(stRead); // see if we're done with headers phop->bSentHeader = (bool)(phop->lSeek >= (long) strlen(phop->req1)); // reset lSeek if done to make it easier for file operations if (phop->bSentHeader) phop->lSeek = 0; return stRead; // } } // now for file to read in (if any), also don't bother if this request // was just enough for the header (which was taken care of above) if (phop->fileIn) { // note we'll have to fudge lSeek a little if there was // also a header, just use a temp var //size_t stOld = stRead; // we'll want to save the ptr location of last stRead // keep a separate pointer to "bump ahead" the pointer for the file data // as ptr may have been used above for the header info //unsigned char *ptr2; // get the file seek offset, both from the offset requested (added) // as well as the size of the header above discounted // - ((phop->req1 && stRead>0) ? stRead : // (phop->req1 ? strlen(phop->req1) : 0L)) long lFileSeek = phop->lSeek + (long) phop->file_offset; fseek(phop->fileIn, lFileSeek, SEEK_SET); if (!feof(phop->fileIn)) { // CMC TODO: better error checking for size*nmemb // i.e. that we don't go overbounds of the file etc, we can check against // content_length (which includes the strlen(req1) also) // note the use of stOld to get to the right position in case the header was read in above //ptr2 = (unsigned char*)ptr +(int)stOld; stRead = fread(ptr, 1, stSend, phop->fileIn); } phop->lSeek += (long) stRead; // increment lSeek to new position phop->bytes_xferred += (double)(stRead); } phop->update_speed(); // this should update the transfer speed return stRead; } void HTTP_OP::setupProxyCurl() { // CMC: use the libcurl proxy routines with this object's proxy information struct /* PROXY_INFO pi useful members: pi.http_server_name pi.http_server_port pi.http_user_name pi.http_user_passwd pi.socks5_user_name pi.socks5_user_passwd pi.socks_server_name pi.socks_server_port pi.socks_version pi.use_http_auth pi.use_http_proxy pi.use_socks_proxy 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 */ CURLcode curlErr; // CMC Note: the string szCurlProxyUserPwd 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 memset(szCurlProxyUserPwd,0x00,128); if (pi.use_http_proxy) { // setup a basic http proxy curlErr = curl_easy_setopt(curlEasy, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); curlErr = curl_easy_setopt(curlEasy, CURLOPT_PROXYPORT, (long) pi.http_server_port); curlErr = curl_easy_setopt(curlEasy, CURLOPT_PROXY, (char*) pi.http_server_name); if (pi.use_http_auth) { /* testing! fprintf(stdout, "Using httpauth for proxy: %s:%d %s:%s\n", pi.http_server_name, pi.http_server_port, pi.http_user_name, pi.http_user_passwd); */ sprintf(szCurlProxyUserPwd, "%s:%s", pi.http_user_name, pi.http_user_passwd); curlErr = curl_easy_setopt(curlEasy, CURLOPT_PROXYAUTH, CURLAUTH_ANY); curlErr = curl_easy_setopt(curlEasy, CURLOPT_PROXYUSERPWD, szCurlProxyUserPwd); } } else { if (pi.use_socks_proxy) { //pi.socks_version -- picks between socks5 & socks4 -- but libcurl only socks5! curlErr = curl_easy_setopt(curlEasy, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5); curlErr = curl_easy_setopt(curlEasy, CURLOPT_PROXYPORT, (long) pi.socks_server_port); curlErr = curl_easy_setopt(curlEasy, CURLOPT_PROXY, (char*) pi.socks_server_name); if ( strlen(pi.socks5_user_passwd)>0 || strlen(pi.socks5_user_name)>0 ) { sprintf(szCurlProxyUserPwd, "%s:%s", pi.socks5_user_name, pi.socks5_user_passwd); curlErr = curl_easy_setopt(curlEasy, CURLOPT_PROXYAUTH, CURLAUTH_ANY); curlErr = curl_easy_setopt(curlEasy, CURLOPT_PROXYUSERPWD, szCurlProxyUserPwd); } } } } const char *BOINC_RCSID_57f273bb60 = "$Id$";