static int auth_header_callback(void *ctx, char ***hdrs) { s3_auth_data *ad = (s3_auth_data *) ctx; time_t now = time(NULL); #ifdef HAVE_GMTIME_R struct tm tm_buffer; struct tm *tm = gmtime_r(&now, &tm_buffer); #else struct tm *tm = gmtime(&now); #endif kstring_t message = { 0, 0, NULL }; unsigned char digest[DIGEST_BUFSIZ]; size_t digest_len; if (!hdrs) { // Closing connection free_auth_data(ad); return 0; } if (now - ad->auth_time < AUTH_LIFETIME) { // Last auth string should still be valid *hdrs = NULL; return 0; } strftime(ad->date, sizeof(ad->date), "Date: %a, %d %b %Y %H:%M:%S GMT", tm); if (!ad->id.l || !ad->secret.l) { ad->auth_time = now; return copy_auth_headers(ad, hdrs); } if (ksprintf(&message, "%s\n\n\n%s\n%s%s%s/%s", ad->mode == 'r' ? "GET" : "PUT", ad->date + 6, ad->token.l ? "x-amz-security-token:" : "", ad->token.l ? ad->token.s : "", ad->token.l ? "\n" : "", ad->bucket) < 0) { return -1; } digest_len = s3_sign(digest, &ad->secret, &message); ad->auth_hdr.l = 0; if (ksprintf(&ad->auth_hdr, "Authorization: AWS %s:", ad->id.s) < 0) goto fail; base64_kput(digest, digest_len, &ad->auth_hdr); free(message.s); ad->auth_time = now; return copy_auth_headers(ad, hdrs); fail: free(message.s); return -1; }
static int add_s3_settings(hFILE_libcurl *fp, const char *s3url, kstring_t *message) { int ret, save; const char *bucket, *path; char date_hdr[40]; CURLcode err; kstring_t url = { 0, 0, NULL }; kstring_t profile = { 0, 0, NULL }; kstring_t id = { 0, 0, NULL }; kstring_t secret = { 0, 0, NULL }; kstring_t token = { 0, 0, NULL }; kstring_t token_hdr = { 0, 0, NULL }; kstring_t auth_hdr = { 0, 0, NULL }; time_t now = time(NULL); #ifdef HAVE_GMTIME_R struct tm tm_buffer; struct tm *tm = gmtime_r(&now, &tm_buffer); #else struct tm *tm = gmtime(&now); #endif strftime(date_hdr, sizeof date_hdr, "Date: %a, %d %b %Y %H:%M:%S GMT", tm); if (add_header(fp, date_hdr) < 0) goto error; kputs(&date_hdr[6], message); kputc('\n', message); // Our S3 URL format is s3[+SCHEME]://[ID[:SECRET[:TOKEN]]@]BUCKET/PATH if (s3url[2] == '+') { bucket = strchr(s3url, ':') + 1; kputsn(&s3url[3], bucket - &s3url[3], &url); } else { kputs("https:", &url); bucket = &s3url[3]; } while (*bucket == '/') kputc(*bucket++, &url); path = bucket + strcspn(bucket, "/?#@"); if (*path == '@') { const char *colon = strpbrk(bucket, ":@"); if (*colon != ':') { urldecode_kput(bucket, colon - bucket, fp, &profile); } else { const char *colon2 = strpbrk(&colon[1], ":@"); urldecode_kput(bucket, colon - bucket, fp, &id); urldecode_kput(&colon[1], colon2 - &colon[1], fp, &secret); if (*colon2 == ':') urldecode_kput(&colon2[1], path - &colon2[1], fp, &token); } bucket = &path[1]; path = bucket + strcspn(bucket, "/?#"); } else { // If the URL has no ID[:SECRET]@, consider environment variables. const char *v; if ((v = getenv("AWS_ACCESS_KEY_ID")) != NULL) kputs(v, &id); if ((v = getenv("AWS_SECRET_ACCESS_KEY")) != NULL) kputs(v, &secret); if ((v = getenv("AWS_SESSION_TOKEN")) != NULL) kputs(v, &token); if ((v = getenv("AWS_DEFAULT_PROFILE")) != NULL) kputs(v, &profile); else if ((v = getenv("AWS_PROFILE")) != NULL) kputs(v, &profile); else kputs("default", &profile); } // Use virtual hosted-style access if possible, otherwise path-style. if (is_dns_compliant(bucket, path)) { kputsn(bucket, path - bucket, &url); kputs(".s3.amazonaws.com", &url); } else { kputs("s3.amazonaws.com/", &url); kputsn(bucket, path - bucket, &url); } kputs(path, &url); if (id.l == 0) { const char *v = getenv("AWS_SHARED_CREDENTIALS_FILE"); parse_ini(v? v : "~/.aws/credentials", profile.s, "aws_access_key_id", &id, "aws_secret_access_key", &secret, "aws_session_token", &token, NULL); } if (id.l == 0) parse_ini("~/.s3cfg", profile.s, "access_key", &id, "secret_key", &secret, "access_token", &token, NULL); if (id.l == 0) parse_simple("~/.awssecret", &id, &secret); if (token.l > 0) { kputs("x-amz-security-token:", message); kputs(token.s, message); kputc('\n', message); kputs("X-Amz-Security-Token: ", &token_hdr); kputs(token.s, &token_hdr); if (add_header(fp, token_hdr.s) < 0) goto error; } kputc('/', message); kputs(bucket, message); // CanonicalizedResource is '/' + bucket + path err = curl_easy_setopt(fp->easy, CURLOPT_URL, url.s); if (err != CURLE_OK) { errno = easy_errno(fp->easy, err); goto error; } // If we have no id/secret, we can't sign the request but will // still be able to access public data sets. if (id.l > 0 && secret.l > 0) { unsigned char digest[DIGEST_BUFSIZ]; size_t digest_len = s3_sign(digest, &secret, message); kputs("Authorization: AWS ", &auth_hdr); kputs(id.s, &auth_hdr); kputc(':', &auth_hdr); base64_kput(digest, digest_len, &auth_hdr); if (add_header(fp, auth_hdr.s) < 0) goto error; } ret = 0; goto free_and_return; error: ret = -1; free_and_return: save = errno; free(url.s); free(profile.s); free(id.s); free(secret.s); free(token.s); free(token_hdr.s); free(auth_hdr.s); free(message->s); errno = save; return ret; }