From 8b10de5559ef9959e22e722ad70c5e4ec2229983 Mon Sep 17 00:00:00 2001 From: "mooredan@suncup.net" Date: Fri, 26 Nov 2010 22:11:48 +0000 Subject: [PATCH] Added an additional check in check_service to expose the curl compiled with openssl vs. nss issue. If the issue is seen, emit an informational message and give the user an option to over-ride checking of the hostname -- it's recommended not to use bucket names with periods and https As implied, added an option ssl_verify_hostname=[0|1] Tested on fedora 14. Will check on Ubuntu/Debian/CentOS after check in. Resolves issue #128 git-svn-id: http://s3fs.googlecode.com/svn/trunk@270 df820570-a93a-0410-bd06-b72b767a4274 --- configure.ac | 2 +- src/s3fs.cpp | 221 +++++++++++++++++++++++++++++++++++++++++++++++++-- src/s3fs.h | 1 + 3 files changed, 215 insertions(+), 9 deletions(-) diff --git a/configure.ac b/configure.ac index 39e4332..aa4a20c 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,7 @@ dnl Process this file with autoconf to produce a configure script. AC_PREREQ(2.59) -AC_INIT(s3fs, 1.18) +AC_INIT(s3fs, 1.19) AC_CANONICAL_SYSTEM diff --git a/src/s3fs.cpp b/src/s3fs.cpp index fa435b0..6064fef 100644 --- a/src/s3fs.cpp +++ b/src/s3fs.cpp @@ -310,10 +310,17 @@ static int my_curl_easy_perform(CURL* curl, FILE* f = 0) { curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL , &url); if(debug) syslog(LOG_DEBUG, "connecting to URL %s", url); + // curl_easy_setopt(curl, CURLOPT_VERBOSE, true); + if (ssl_verify_hostname.substr(0,1) == "0") { + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); + } + if (curl_ca_bundle.size() != 0) { curl_easy_setopt(curl, CURLOPT_CAINFO, curl_ca_bundle.c_str()); } + size_t first_pos = string::npos; + // 1 attempt + retries... int t = retries + 1; while (t-- > 0) { @@ -354,12 +361,31 @@ static int my_curl_easy_perform(CURL* curl, FILE* f = 0) { curlCode, curl_easy_strerror(curlCode)); exit(1); - break; - default: - // Unknown error - return - syslog(LOG_ERR, "###curlCode: %i msg: %s", curlCode, - curl_easy_strerror(curlCode));; - break; + break; + + case CURLE_PEER_FAILED_VERIFICATION: + first_pos = bucket.find_first_of("."); + if (first_pos != string::npos) { + fprintf (stderr, "%s: curl returned a CURL_PEER_FAILED_VERIFICATION error\n", program_name.c_str()); + fprintf (stderr, "%s: security issue found: buckets with periods in their name are incompatible with https\n", program_name.c_str()); + fprintf (stderr, "%s: This check can be over-ridden by using the -o ssl_verify_hostname=0\n", program_name.c_str()); + fprintf (stderr, "%s: The certificate will still be checked but the hostname will not be verified.\n", program_name.c_str()); + fprintf (stderr, "%s: A more secure method would be to use a bucket name without periods.\n", program_name.c_str()); + } else { + fprintf (stderr, "%s: my_curl_easy_perform: curlCode: %i -- %s\n", + program_name.c_str(), + curlCode, + curl_easy_strerror(curlCode)); + } + exit(1); + break; + + default: + // Unknown error - return + syslog(LOG_ERR, "###curlCode: %i msg: %s", curlCode, + curl_easy_strerror(curlCode));; + exit(1); + break; } } syslog(LOG_ERR, "###retrying..."); @@ -1485,6 +1511,9 @@ static int s3fs_readdir( curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, true); curl_easy_setopt(curl_handle, CURLOPT_NOBODY, true); // HEAD curl_easy_setopt(curl_handle, CURLOPT_FILETIME, true); // Last-Modified + if (ssl_verify_hostname.substr(0,1) == "0") { + curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0); + } if (curl_ca_bundle.size() != 0) { curl_easy_setopt(curl_handle, CURLOPT_CAINFO, curl_ca_bundle.c_str()); } @@ -1732,6 +1761,9 @@ static void s3fs_check_service(void) { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback); curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + if (ssl_verify_hostname.substr(0,1) == "0") { + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); + } auto_curl_slist headers; string date = get_date(); @@ -1782,6 +1814,15 @@ static void s3fs_check_service(void) { curl_easy_strerror(curlCode)); exit(1); break; + + case CURLE_PEER_FAILED_VERIFICATION: + fprintf (stderr, "%s: s3fs_check_service: curlCode: %i -- %s\n", + program_name.c_str(), + curlCode, + curl_easy_strerror(curlCode)); + exit(1); + break; + default: // Unknown error - return syslog(LOG_ERR, "curlCode: %i msg: %s", curlCode, @@ -1902,7 +1943,135 @@ static void s3fs_check_service(void) { exit(1); } - // cout << "responseText: " << responseText << endl; + // once we arrive here, that means that our preliminary connection + // worked and the bucket matches the credentials provided + // now check for bucket location using the virtual host name + // this should expose the certificate mismatch that may occur + // when using https:// (SSL) and a bucket name that contains periods + resource = urlEncode(service_path + bucket); + url = host + resource + "?location"; + + // printf("resource: %s\n", resource.c_str()); + // printf("bucket: %s\n", bucket.c_str()); + // printf("service_path: %s\n", service_path.c_str()); + // printf("url: %s\n", url.c_str()); + // printf("host: %s\n", host.c_str()); + + string my_url = prepare_url(url.c_str()); + + // printf("my_url: %s\n", my_url.c_str()); + + // curl_easy_setopt(curl, CURLOPT_VERBOSE, true); + curl_easy_setopt(curl, CURLOPT_URL, my_url.c_str()); + + auto_curl_slist new_headers; + date = get_date(); + new_headers.append("Date: " + date); + new_headers.append("Authorization: AWS " + AWSAccessKeyId + ":" + + calc_signature("GET", "", date, new_headers.get(), resource + "/?location")); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, new_headers.get()); + + // Need to know if the curl response is just a timeout possibly + // indicating the the network is down or if the connection was + // acutally made - my_curl_easy_perform doesn't differentiate + // between the two + responseText.clear(); + + size_t first_pos = string::npos; + + t = retries + 1; + while (t-- > 0) { + curlCode = curl_easy_perform(curl.get()); + if (curlCode == 0) { + break; + } + if (curlCode != CURLE_OPERATION_TIMEDOUT) { + if (curlCode == CURLE_HTTP_RETURNED_ERROR) { + break; + } else { + switch (curlCode) { + case CURLE_SSL_CACERT: + // try to locate cert, if successful, then set the + // option and continue + if (curl_ca_bundle.size() == 0) { + locate_bundle(); + if (curl_ca_bundle.size() != 0) { + t++; + curl_easy_setopt(curl, CURLOPT_CAINFO, curl_ca_bundle.c_str()); + continue; + } + } + syslog(LOG_ERR, "curlCode: %i msg: %s", curlCode, + curl_easy_strerror(curlCode));; + fprintf (stderr, "%s: curlCode: %i -- %s\n", + program_name.c_str(), + curlCode, + curl_easy_strerror(curlCode)); + exit(1); + break; + + case CURLE_PEER_FAILED_VERIFICATION: + first_pos = bucket.find_first_of("."); + if (first_pos != string::npos) { + fprintf (stderr, "%s: curl returned a CURL_PEER_FAILED_VERIFICATION error\n", program_name.c_str()); + fprintf (stderr, "%s: security issue found: buckets with periods in their name are incompatible with https\n", program_name.c_str()); + fprintf (stderr, "%s: This check can be over-ridden by using the -o ssl_verify_hostname=0\n", program_name.c_str()); + fprintf (stderr, "%s: The certificate will still be checked but the hostname will not be verified.\n", program_name.c_str()); + fprintf (stderr, "%s: A more secure method would be to use a bucket name without periods.\n", program_name.c_str()); + } else { + fprintf (stderr, "%s: my_curl_easy_perform: curlCode: %i -- %s\n", + program_name.c_str(), + curlCode, + curl_easy_strerror(curlCode)); + } + exit(1); + break; + + default: + // Unknown error - return + syslog(LOG_ERR, "curlCode: %i msg: %s", curlCode, + curl_easy_strerror(curlCode));; + return; + } + } + } + } + + // We get here under three conditions: + // - too many timeouts + // - connection, but a HTTP error + // - success + + if(debug) { + syslog(LOG_DEBUG, "curlCode: %i msg: %s\n", + curlCode, curl_easy_strerror(curlCode)); + } + + // network is down + if (curlCode == CURLE_OPERATION_TIMEDOUT) { + return; + } + + curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &responseCode); + if(debug) syslog(LOG_DEBUG, "responseCode: %i\n", (int)responseCode); + + // Connection was made, but there is a HTTP error + if (curlCode == CURLE_HTTP_RETURNED_ERROR) { + if (responseCode == 403) { + fprintf (stderr, "%s: HTTP: 403 Forbidden - it is likely that your credentials are invalid\n", + program_name.c_str()); + exit(1); + } + fprintf (stderr, "%s: HTTP: %i - report this to the s3fs developers\n", + program_name.c_str(), (int)responseCode); + exit(1); + } + + // Success + if (responseCode != 200) { + if(debug) syslog(LOG_DEBUG, "responseCode: %i\n", (int)responseCode); + return; + } return; } @@ -2324,6 +2493,17 @@ static int my_fuse_opt_proc(void *data, const char *arg, int key, struct fuse_ar exit(1); } } + if (strstr(arg, "ssl_verify_hostname=") != 0) { + ssl_verify_hostname = strchr(arg, '=') + 1; + if (strcmp(ssl_verify_hostname.c_str(), "1") == 0 || + strcmp(ssl_verify_hostname.c_str(), "0") == 0 ) { + return 0; + } else { + fprintf(stderr, "%s: poorly formed argument to option: ssl_verify_hostname\n", + program_name.c_str()); + exit(1); + } + } if (strstr(arg, "passwd_file=") != 0) { passwd_file = strchr(arg, '=') + 1; return 0; @@ -2397,7 +2577,7 @@ int main(int argc, char *argv[]) { {0, 0, 0, 0}}; // get progam name - emulate basename - size_t found; + size_t found = string::npos; program_name.assign(argv[0]); found = program_name.find_last_of("/"); if(found != string::npos) { @@ -2498,6 +2678,31 @@ int main(int argc, char *argv[]) { // There's room for more command line error checking + // Check to see if the bucket name contains periods and https (SSL) is + // being used. This is a known limitation: + // http://docs.amazonwebservices.com/AmazonS3/latest/dev/ + // The Developers Guide suggests that either use HTTP of for us to write + // our own certificate verification logic. + // For now, this will be unsupported unless we get a request for it to + // be supported. In that case, we have a couple of options: + // - implement a command line option that bypasses the verify host + // but doesn't bypass verifying the certificate + // - write our own host verification (this might be complex) + // See issue #128 + + /* + if (ssl_verify_hostname.substr(0,1) == "1") { + found = bucket.find_first_of("."); + if(found != string::npos) { + found = host.find("https:"); + if(found != string::npos) { + fprintf(stderr, "%s: Using https and a bucket name with periods is unsupported.\n", + program_name.c_str()); + exit(1); + } + } + } + */ // Does the bucket exist? diff --git a/src/s3fs.h b/src/s3fs.h index 35b6ffe..9da9ddb 100644 --- a/src/s3fs.h +++ b/src/s3fs.h @@ -58,6 +58,7 @@ static bool debug = 0; // if .size()==0 then local file cache is disabled static string use_cache; static string use_rrs; +static string ssl_verify_hostname = "1"; static string public_bucket; // TODO(apetresc): make this an enum