Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

396 lines (346 sloc) 13.478 kB
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h> /* close */
#include <assert.h>
#include <string.h>
#include <strings.h>
#include <fcntl.h> /* open */
#include <curl/curl.h>
#include <curl/easy.h>
#if defined(HAVE_ZLIB_H)
#include <zlib.h>
#endif
#include "euca_auth.h"
#include "eucalyptus.h"
#include "misc.h"
#include "walrus.h"
#define TOTAL_RETRIES 5 /* download is retried in case of connection problems */
#define FIRST_TIMEOUT 4 /* in seconds, goes in powers of two afterwards */
#define CHUNK 262144 /* buffer size for decompression operations */
#define BUFSIZE 4096 /* should be big enough for CERT and the signature */
#define STRSIZE 245 /* for short strings: files, hosts, URLs */
#define WALRUS_ENDPOINT "/services/Walrus"
#define DEFAULT_HOST_PORT "localhost:8773"
#define GET_IMAGE_CMD "GetDecryptedImage"
#define GET_OBJECT_CMD "GetObject"
static size_t write_data (void *buffer, size_t size, size_t nmemb, void *userp);
static size_t write_header (void *buffer, size_t size, size_t nmemb, void *userp);
#if defined(ZLIB_VERNUM) && (ZLIB_VERNUM >= 0x1204)
static size_t write_data_zlib (void *buffer, size_t size, size_t nmemb, void *userp);
static void zerr (int ret, char * where);
#define CAN_GZIP
#endif
struct request {
FILE * fp; /* output file pointer to be used by curl WRITERs */
long long total_wrote; /* bytes written during the operation */
long long total_calls; /* write calls made during the operation */
#if defined (CAN_GZIP)
z_stream strm; /* stream struct used by zlib */
int ret; /* return value of last inflate() call */
#endif
};
/* downloads a decrypted image from Walrus based on the manifest URL,
* saves it to outfile */
static int walrus_request (const char * walrus_op, const char * verb, const char * requested_url, const char * outfile, const int do_compress)
{
int code = ERROR;
char url [BUFSIZE];
strncpy (url, requested_url, BUFSIZE);
#if defined(CAN_GZIP)
if (do_compress)
snprintf (url, BUFSIZE, "%s%s", requested_url, "?IsCompressed=true");
#endif
logprintfl (EUCAINFO, "walrus_request(): downloading %s\n", outfile);
logprintfl (EUCAINFO, " from %s\n", url);
/* isolate the PATH in the URL as it will be needed for signing */
char * url_path;
if (strncasecmp (url, "http://", 7)!=0) {
logprintfl (EUCAERROR, "walrus_request(): URL must start with http://...\n");
return code;
}
if ((url_path=strchr(url+7, '/'))==NULL) { /* find first '/' after hostname */
logprintfl (EUCAERROR, "walrus_request(): URL has no path\n");
return code;
}
if (euca_init_cert()) {
logprintfl (EUCAERROR, "walrus_request(): failed to initialize certificate\n");
return code;
}
FILE * fp = fopen64 (outfile, "w");
if (fp==NULL) {
logprintfl (EUCAERROR, "walrus_request(): failed to open %s for writing\n", outfile);
return code;
}
CURL * curl;
CURLcode result;
curl = curl_easy_init ();
if (curl==NULL) {
logprintfl (EUCAERROR, "walrus_request(): could not initialize libcurl\n");
return code;
}
char error_msg [CURL_ERROR_SIZE];
curl_easy_setopt (curl, CURLOPT_ERRORBUFFER, error_msg);
curl_easy_setopt (curl, CURLOPT_URL, url);
curl_easy_setopt (curl, CURLOPT_HEADERFUNCTION, write_header);
if (strncmp (verb, "GET", 4)==0) {
curl_easy_setopt (curl, CURLOPT_HTTPGET, 1L);
} else if (strncmp (verb, "HEAD", 5)==0) {
/* TODO: HEAD isn't very useful atm since we don't look at headers */
curl_easy_setopt (curl, CURLOPT_NOBODY, 1L);
} else {
logprintfl (EUCAERROR, "walrus_request(): invalid HTTP verb %s\n", verb);
return ERROR; /* TODO: dealloc structs before returning! */
}
/* set up the default write function, but possibly override
* it below, if compression is desired and possible */
struct request params;
params.fp = fp;
curl_easy_setopt (curl, CURLOPT_WRITEDATA, &params);
curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, write_data);
#if defined(CAN_GZIP)
if (do_compress) {
curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, write_data_zlib);
}
#endif
struct curl_slist * headers = NULL; /* beginning of a DLL with headers */
headers = curl_slist_append (headers, "Authorization: Euca");
char op_hdr [STRSIZE];
if(walrus_op != NULL) {
snprintf (op_hdr, STRSIZE, "EucaOperation: %s", walrus_op);
headers = curl_slist_append (headers, op_hdr);
}
time_t t = time(NULL);
char * date_str = asctime(localtime(&t)); /* points to a static area */
if (date_str==NULL) return ERROR;
assert (strlen(date_str)+7<=STRSIZE);
date_str [strlen(date_str)-1] = '\0'; /* trim off the newline */
char date_hdr [STRSIZE];
snprintf (date_hdr, STRSIZE, "Date: %s", date_str);
headers = curl_slist_append (headers, date_hdr);
char * cert_str = euca_get_cert (0); /* read the cloud-wide cert */
if (cert_str==NULL) return ERROR;
char * cert64_str = base64_enc ((unsigned char *)cert_str, strlen(cert_str));
assert (strlen(cert64_str)+11<=BUFSIZE);
char cert_hdr [BUFSIZE];
snprintf (cert_hdr, BUFSIZE, "EucaCert: %s", cert64_str);
logprintfl (EUCADEBUG2, "walrus_request(): base64 certificate, %s\n", get_string_stats(cert64_str));
headers = curl_slist_append (headers, cert_hdr);
free (cert_str);
char * sig_str = euca_sign_url (verb, date_str, url_path); /* create Walrus-compliant sig */
if (sig_str==NULL) return ERROR;
assert (strlen(sig_str)+16<=BUFSIZE);
char sig_hdr [BUFSIZE];
snprintf (sig_hdr, BUFSIZE, "EucaSignature: %s", sig_str);
headers = curl_slist_append (headers, sig_hdr);
curl_easy_setopt (curl, CURLOPT_HTTPHEADER, headers); /* register headers */
if (walrus_op) {
logprintfl (EUCADEBUG, "walrus_request(): writing %s/%s output to %s\n", verb, walrus_op, outfile);
} else {
logprintfl (EUCADEBUG, "walrus_request(): writing %s output to %s\n", verb, outfile);
}
int retries = TOTAL_RETRIES;
int timeout = FIRST_TIMEOUT;
do {
params.total_wrote = 0L;
params.total_calls = 0L;
#if defined(CAN_GZIP)
if (do_compress) {
/* allocate zlib inflate state */
params.strm.zalloc = Z_NULL;
params.strm.zfree = Z_NULL;
params.strm.opaque = Z_NULL;
params.strm.avail_in = 0;
params.strm.next_in = Z_NULL;
params.ret = inflateInit2 (&(params.strm), 31);
if (params.ret != Z_OK) {
zerr (params.ret, "walrus_request");
break;
}
}
#endif
result = curl_easy_perform (curl); /* do it */
logprintfl (EUCADEBUG, "walrus_request(): wrote %ld bytes in %ld writes\n", params.total_wrote, params.total_calls);
#if defined(CAN_GZIP)
if (do_compress) {
inflateEnd(&(params.strm));
if (params.ret != Z_STREAM_END) {
zerr (params.ret, "walrus_request");
}
}
#endif
if (result) { // curl error (connection or transfer failed)
logprintfl (EUCAERROR, "walrus_request(): %s (%d)\n", error_msg, result);
if (retries > 0) {
logprintfl (EUCAERROR, " download retry %d of %d will commence in %d seconds\n", retries, TOTAL_RETRIES, timeout);
}
sleep (timeout);
fseek (fp, 0L, SEEK_SET);
timeout <<= 1;
retries--;
} else {
retries = 0; // do not retry if we go a proper HTTP response
long httpcode;
curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &httpcode);
/* TODO: pull out response message, too */
switch (httpcode) {
case 200L: /* all good */
logprintfl (EUCAINFO, "walrus_request(): saved image in %s\n", outfile);
code = OK;
break;
default: /* some kind of error */
logprintfl (EUCAERROR, "walrus_request(): server responded with HTTP code %ld\n", httpcode);
logcat (EUCADEBUG, outfile); /* dump the error from outfile into the log */
}
}
} while (code!=OK && retries>0);
fclose (fp);
if ( code != OK ) {
logprintfl (EUCAINFO, "walrus_request(): due to error, removing %s\n", outfile);
remove (outfile);
}
free (cert64_str);
free (sig_str);
curl_slist_free_all (headers);
curl_easy_cleanup (curl);
return code;
}
/* downloads a Walrus object from the URL, saves it to outfile */
int walrus_object_by_url (const char * url, const char * outfile, const int do_compress)
{
return walrus_request (NULL, "GET", url, outfile, do_compress);
}
/* downloads a Walrus object from the default Walrus endpoint,
* so only the path is needed; saves object to outfile */
int walrus_object_by_path (const char * path, const char * outfile, const int do_compress)
{
char url [STRSIZE];
snprintf (url, STRSIZE, "http://%s%s/%s", DEFAULT_HOST_PORT, WALRUS_ENDPOINT, path);
return walrus_object_by_url (url, outfile, do_compress);
}
/* downloads a decrypted image from Walrus based on the manifest URL,
* saves it to outfile */
int walrus_image_by_manifest_url (const char * url, const char * outfile, const int do_compress)
{
return walrus_request (GET_IMAGE_CMD, "GET", url, outfile, do_compress);
}
/* gets a decrypted image from the default Walrus endpoint,
* so only manifest path is needed; saves image to outfile */
int walrus_image_by_manifest_path (const char * manifest_path, const char * outfile, const int do_compress)
{
char url [STRSIZE];
snprintf (url, STRSIZE, "http://%s%s/%s", DEFAULT_HOST_PORT, WALRUS_ENDPOINT, manifest_path);
return walrus_image_by_manifest_url (url, outfile, do_compress);
}
/* downloads a digest of an image and compare it to file at digest_path
* returns 0 if same, -N if different, N if error */
int walrus_verify_digest (const char * url, const char * old_digest)
{
int e = ERROR;
char * new_digest = strdup ("/tmp/walrus-digest-XXXXXX");
int tmp_fd = mkstemp (new_digest);
if (tmp_fd<0) {
logprintfl (EUCAERROR, "error: failed to create a digest file %s\n", new_digest);
} else {
close (tmp_fd); /* walrus routine will reopen the file */
/* download a fresh digest */
if ( (e=walrus_object_by_url (url, new_digest, 0)) != 0 ) {
logprintfl (EUCAERROR, "error: failed to download digest to %s\n", new_digest);
} else {
/* compare the two */
e = diff (new_digest, old_digest);
}
}
unlink (new_digest);
free (new_digest);
return e;
}
/* libcurl header write handler */
static size_t write_header (void *buffer, size_t size, size_t nmemb, void *params)
{
/* here in case we want to do something with headers */
return size * nmemb;
}
/* libcurl write handler */
static size_t write_data (void *buffer, size_t size, size_t nmemb, void *params)
{
assert (params !=NULL);
FILE * fp = ((struct request *)params)->fp;
int wrote = fwrite (buffer, size, nmemb, fp);
((struct request *)params)->total_wrote += wrote;
((struct request *)params)->total_calls++;
return wrote;
}
#if defined(CAN_GZIP)
/* unused testing function */
static void print_data (unsigned char *buf, const int size)
{
int i;
for (i=0; i<size; i++) {
int c = buf [i];
if (c>' ' && c<='~')
printf (" %c", c);
else
printf (" %x", c);
}
printf ("\n");
}
/* report on a zlib error */
static void zerr (int ret, char * where)
{
switch (ret) {
case Z_ERRNO:
logprintfl (EUCAERROR, "error: %s(): zlib: failed to write\n", where);
break;
case Z_STREAM_ERROR:
logprintfl (EUCAERROR, "error: %s(): zlib: invalid compression level\n", where);
break;
case Z_DATA_ERROR:
logprintfl (EUCAERROR, "error: %s(): zlib: invalid or incomplete deflate data\n", where);
break;
case Z_MEM_ERROR:
logprintfl (EUCAERROR, "error: %s(): zlib: out of memory\n", where);
break;
case Z_VERSION_ERROR:
logprintfl (EUCAERROR, "error: %s(): zlib: zlib version mismatch!\n", where);
}
}
/* libcurl write handler for gzipped streams */
static size_t write_data_zlib (void *buffer, size_t size, size_t nmemb, void *params)
{
assert (params !=NULL);
z_stream * strm = &(((struct request *)params)->strm);
FILE * fp = ((struct request *)params)->fp;
unsigned char out [CHUNK];
int wrote = 0;
int ret;
strm->avail_in = size * nmemb;
strm->next_in = (unsigned char *)buffer;
do {
strm->avail_out = CHUNK;
strm->next_out = out;
((struct request *)params)->ret = ret = inflate (strm, Z_NO_FLUSH);
switch (ret) {
case Z_NEED_DICT:
ret = Z_DATA_ERROR; // ok to fall through
case Z_DATA_ERROR:
case Z_MEM_ERROR:
case Z_STREAM_ERROR:
inflateEnd(strm);
zerr (ret, "write_data_zlib");
return ret;
}
unsigned have = CHUNK - strm->avail_out;
if (fwrite (out, 1, have, fp) != have || ferror(fp)) {
logprintfl (EUCAERROR, "error: write_data_zlib(): failed to write\n");
inflateEnd(strm);
return Z_ERRNO;
}
wrote += have;
} while (strm->avail_out == 0);
((struct request *)params)->total_wrote += wrote;
((struct request *)params)->total_calls++;
return size * nmemb;
}
#endif /* CAN_GZIP */
Jump to Line
Something went wrong with that request. Please try again.