mariadb/extra/mariabackup/xbcloud.cc
Hugo Wen 7bdd878ae4 Fix few vulnerabilities found by Cppcheck
While performing SAST scanning using Cppcheck against source code of
commit 81196469, several code vulnerabilities were found.

Fix following issues:

1. Parameters of `snprintf` function are incorrect.

   Cppcheck error:

       client/mysql_plugin.c:1228: error: snprintf format string requires 6 parameters but only 5 are given.

   It is due to commit 630d7229 introduced option `--lc-messages-dir`
   in the bootstrap command. However the parameter was not even given
   in the `snprintf` after changing the format string.

   Fix:
   Restructure the code logic and correct the function parameters for
   `snprintf`.

2. Null pointer is used in a `snprintf` which could cause a crash.

   Cppcheck error:

       extra/mariabackup/xbcloud.cc:2534: error: Null pointer dereference

   The code intended to print the swift_project name, if the
   opt_swift_project_id is NULL but opt_swift_project is not NULL.
   However the parameter of `snprintf` was mistakenly using
   `opt_swift_project_id`.

   Fix:
   Change to use the correct string from `opt_swift_project`.

3. Potential double release of a memory

   Cppcheck error:

       plugin/auth_pam/testing/pam_mariadb_mtr.c:69: error: Memory pointed to by 'resp' is freed twice.

   A pointer `resp` is reused and allocated new memory after it has been
   freed. However, `resp` was not set to NULL after freed.
   Potential double release of the same pointer if the call back
   function doesn't allocate new memory for `resp` pointer.

   Fix:
   Set the `resp` pointer to NULL after the first free() to make sure
   the same address is not freed twice.

All new code of the whole pull request, including one or several files
that are either new files or modified ones, are contributed under the
BSD-new license. I am contributing on behalf of my employer Amazon Web
Services, Inc.
2023-03-02 14:38:24 +11:00

2722 lines
68 KiB
C++

/******************************************************
Copyright (c) 2014 Percona LLC and/or its affiliates.
The xbstream utility: serialize/deserialize files in the XBSTREAM format.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
*******************************************************/
#include <my_global.h>
#include <my_default.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
#include <ev.h>
#include <unistd.h>
#include <errno.h>
#include <gcrypt.h>
#include <assert.h>
#include <my_sys.h>
#include <my_dir.h>
#include <my_getopt.h>
#include <algorithm>
#include <map>
#include <string>
#include <jsmn.h>
#include "xbstream.h"
using std::min;
using std::max;
using std::map;
using std::string;
#define XBCLOUD_VERSION "1.0"
#define SWIFT_MAX_URL_SIZE 8192
#define SWIFT_MAX_HDR_SIZE 8192
#define SWIFT_CHUNK_SIZE 11 * 1024 * 1024
#if ((LIBCURL_VERSION_MAJOR >= 7) && (LIBCURL_VERSION_MINOR >= 16))
#define OLD_CURL_MULTI 0
#else
#define OLD_CURL_MULTI 1
#endif
/*****************************************************************************/
typedef struct swift_auth_info_struct swift_auth_info;
typedef struct connection_info_struct connection_info;
typedef struct socket_info_struct socket_info;
typedef struct global_io_info_struct global_io_info;
typedef struct slo_chunk_struct slo_chunk;
typedef struct container_list_struct container_list;
typedef struct object_info_struct object_info;
struct swift_auth_info_struct {
char url[SWIFT_MAX_URL_SIZE];
char token[SWIFT_MAX_HDR_SIZE];
};
struct global_io_info_struct {
struct ev_loop *loop;
struct ev_io input_event;
struct ev_timer timer_event;
CURLM *multi;
int still_running;
int eof;
curl_socket_t input_fd;
connection_info **connections;
long chunk_no;
connection_info *current_connection;
const char *url;
const char *container;
const char *token;
const char *backup_name;
};
struct socket_info_struct {
curl_socket_t sockfd;
CURL *easy;
int action;
long timeout;
struct ev_io ev;
int evset;
global_io_info *global;
};
struct connection_info_struct {
CURL *easy;
global_io_info *global;
char *buffer;
size_t buffer_size;
size_t filled_size;
size_t upload_size;
bool chunk_uploaded;
bool chunk_acked;
char error[CURL_ERROR_SIZE];
struct curl_slist *slist;
char *name;
size_t name_len;
char hash[33];
size_t chunk_no;
bool magic_verified;
size_t chunk_path_len;
xb_chunk_type_t chunk_type;
size_t payload_size;
size_t chunk_size;
int retry_count;
bool upload_started;
ulong global_idx;
};
struct slo_chunk_struct {
char name[SWIFT_MAX_URL_SIZE];
char md5[33];
int idx;
size_t size;
};
struct object_info_struct {
char hash[33];
char name[SWIFT_MAX_URL_SIZE];
size_t bytes;
};
struct container_list_struct {
size_t content_length;
size_t content_bufsize;
char *content_json;
size_t object_count;
size_t idx;
object_info *objects;
bool final;
};
enum {SWIFT, S3};
const char *storage_names[] =
{ "SWIFT", "S3", NullS};
static my_bool opt_verbose = 0;
static ulong opt_storage = SWIFT;
static const char *opt_swift_user = NULL;
static const char *opt_swift_user_id = NULL;
static const char *opt_swift_password = NULL;
static const char *opt_swift_tenant = NULL;
static const char *opt_swift_tenant_id = NULL;
static const char *opt_swift_project = NULL;
static const char *opt_swift_project_id = NULL;
static const char *opt_swift_domain = NULL;
static const char *opt_swift_domain_id = NULL;
static const char *opt_swift_region = NULL;
static const char *opt_swift_container = NULL;
static const char *opt_swift_storage_url = NULL;
static const char *opt_swift_auth_url = NULL;
static const char *opt_swift_key = NULL;
static const char *opt_swift_auth_version = NULL;
static const char *opt_name = NULL;
static const char *opt_cacert = NULL;
static ulong opt_parallel = 1;
static my_bool opt_insecure = 0;
static enum {MODE_GET, MODE_PUT, MODE_DELETE} opt_mode;
static char **file_list = NULL;
static int file_list_size = 0;
TYPELIB storage_typelib =
{array_elements(storage_names)-1, "", storage_names, NULL};
enum {
OPT_STORAGE = 256,
OPT_SWIFT_CONTAINER,
OPT_SWIFT_AUTH_URL,
OPT_SWIFT_KEY,
OPT_SWIFT_USER,
OPT_SWIFT_USER_ID,
OPT_SWIFT_PASSWORD,
OPT_SWIFT_TENANT,
OPT_SWIFT_TENANT_ID,
OPT_SWIFT_PROJECT,
OPT_SWIFT_PROJECT_ID,
OPT_SWIFT_DOMAIN,
OPT_SWIFT_DOMAIN_ID,
OPT_SWIFT_REGION,
OPT_SWIFT_STORAGE_URL,
OPT_SWIFT_AUTH_VERSION,
OPT_PARALLEL,
OPT_CACERT,
OPT_INSECURE,
OPT_VERBOSE
};
static struct my_option my_long_options[] =
{
{"help", '?', "Display this help and exit.",
0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
{"storage", OPT_STORAGE, "Specify storage type S3/SWIFT.",
&opt_storage, &opt_storage, &storage_typelib,
GET_ENUM, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"swift-auth-version", OPT_SWIFT_AUTH_VERSION,
"Swift authentication verison to use.",
&opt_swift_auth_version, &opt_swift_auth_version, 0,
GET_STR_ALLOC, REQUIRED_ARG,
0, 0, 0, 0, 0, 0},
{"swift-container", OPT_SWIFT_CONTAINER,
"Swift container to store backups into.",
&opt_swift_container, &opt_swift_container, 0,
GET_STR_ALLOC, REQUIRED_ARG,
0, 0, 0, 0, 0, 0},
{"swift-user", OPT_SWIFT_USER,
"Swift user name.",
&opt_swift_user, &opt_swift_user, 0, GET_STR_ALLOC, REQUIRED_ARG,
0, 0, 0, 0, 0, 0},
{"swift-user-id", OPT_SWIFT_USER_ID,
"Swift user ID.",
&opt_swift_user_id, &opt_swift_user_id, 0, GET_STR_ALLOC, REQUIRED_ARG,
0, 0, 0, 0, 0, 0},
{"swift-auth-url", OPT_SWIFT_AUTH_URL,
"Base URL of SWIFT authentication service.",
&opt_swift_auth_url, &opt_swift_auth_url, 0,
GET_STR_ALLOC, REQUIRED_ARG,
0, 0, 0, 0, 0, 0},
{"swift-storage-url", OPT_SWIFT_STORAGE_URL,
"URL of object-store endpoint. Usually received from authentication "
"service. Specify to override this value.",
&opt_swift_storage_url, &opt_swift_storage_url, 0,
GET_STR_ALLOC, REQUIRED_ARG,
0, 0, 0, 0, 0, 0},
{"swift-key", OPT_SWIFT_KEY,
"Swift key.",
&opt_swift_key, &opt_swift_key, 0, GET_STR_ALLOC, REQUIRED_ARG,
0, 0, 0, 0, 0, 0},
{"swift-tenant", OPT_SWIFT_TENANT,
"The tenant name. Both the --swift-tenant and --swift-tenant-id "
"options are optional, but should not be specified together.",
&opt_swift_tenant, &opt_swift_tenant, 0, GET_STR_ALLOC, REQUIRED_ARG,
0, 0, 0, 0, 0, 0},
{"swift-tenant-id", OPT_SWIFT_TENANT_ID,
"The tenant ID. Both the --swift-tenant and --swift-tenant-id "
"options are optional, but should not be specified together.",
&opt_swift_tenant_id, &opt_swift_tenant_id, 0,
GET_STR_ALLOC, REQUIRED_ARG,
0, 0, 0, 0, 0, 0},
{"swift-project", OPT_SWIFT_PROJECT,
"The project name.",
&opt_swift_project, &opt_swift_project, 0, GET_STR_ALLOC, REQUIRED_ARG,
0, 0, 0, 0, 0, 0},
{"swift-project-id", OPT_SWIFT_PROJECT_ID,
"The project ID.",
&opt_swift_project_id, &opt_swift_project_id, 0,
GET_STR_ALLOC, REQUIRED_ARG,
0, 0, 0, 0, 0, 0},
{"swift-domain", OPT_SWIFT_DOMAIN,
"The domain name.",
&opt_swift_domain, &opt_swift_domain, 0, GET_STR_ALLOC, REQUIRED_ARG,
0, 0, 0, 0, 0, 0},
{"swift-domain-id", OPT_SWIFT_DOMAIN_ID,
"The domain ID.",
&opt_swift_domain_id, &opt_swift_domain_id, 0,
GET_STR_ALLOC, REQUIRED_ARG,
0, 0, 0, 0, 0, 0},
{"swift-password", OPT_SWIFT_PASSWORD,
"The password of the user.",
&opt_swift_password, &opt_swift_password, 0,
GET_STR_ALLOC, REQUIRED_ARG,
0, 0, 0, 0, 0, 0},
{"swift-region", OPT_SWIFT_REGION,
"The region object-store endpoint.",
&opt_swift_region, &opt_swift_region, 0,
GET_STR_ALLOC, REQUIRED_ARG,
0, 0, 0, 0, 0, 0},
{"parallel", OPT_PARALLEL,
"Number of parallel chunk uploads.",
&opt_parallel, &opt_parallel, 0, GET_ULONG, REQUIRED_ARG,
1, 0, 0, 0, 0, 0},
{"cacert", OPT_CACERT,
"CA certificate file.",
&opt_cacert, &opt_cacert, 0, GET_STR_ALLOC, REQUIRED_ARG,
0, 0, 0, 0, 0, 0},
{"insecure", OPT_INSECURE,
"Do not verify server SSL certificate.",
&opt_insecure, &opt_insecure, 0, GET_BOOL, NO_ARG,
0, 0, 0, 0, 0, 0},
{"verbose", OPT_VERBOSE,
"Turn ON cURL tracing.",
&opt_verbose, &opt_verbose, 0, GET_BOOL, NO_ARG,
0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
};
/* The values of these arguments should be masked
on the command line */
static const char * const masked_args[] = {
"--swift-password",
"--swift-key",
"--swift-auth-url",
"--swift-storage-url",
"--swift-container",
"--swift-user",
"--swift-tenant",
"--swift-user-id",
"--swift-tenant-id",
0
};
static map<string, ulonglong> file_chunk_count;
static
void
print_version()
{
printf("%s Ver %s for %s (%s)\n", my_progname, XBCLOUD_VERSION,
SYSTEM_TYPE, MACHINE_TYPE);
}
static
void
usage()
{
print_version();
puts("Copyright (C) 2015 Percona LLC and/or its affiliates.");
puts("This software comes with ABSOLUTELY NO WARRANTY. "
"This is free software,\nand you are welcome to modify and "
"redistribute it under the GPL license.\n");
puts("Manage backups on Cloud services.\n");
puts("Usage: ");
printf(" %s -c put [OPTIONS...] <NAME> upload backup from STDIN into "
"the cloud service with given name.\n", my_progname);
printf(" %s -c get [OPTIONS...] <NAME> [FILES...] stream specified "
"backup or individual files from cloud service into STDOUT.\n",
my_progname);
puts("\nOptions:");
my_print_help(my_long_options);
}
static
my_bool
get_one_option(int optid, const struct my_option *opt __attribute__((unused)),
char *argument __attribute__((unused)))
{
switch (optid) {
case '?':
usage();
exit(0);
}
return(FALSE);
}
static const char *load_default_groups[]=
{ "xbcloud", 0 };
/*********************************************************************//**
mask sensitive values on the command line */
static
void
mask_args(int argc, char **argv)
{
int i;
for (i = 0; i < argc-1; i++) {
int j = 0;
if (argv[i]) while (masked_args[j]) {
char *p;
if ((p = strstr(argv[i], masked_args[j]))) {
p += strlen(masked_args[j]);
while (*p && *p != '=') {
p++;
}
if (*p == '=') {
p++;
while (*p) {
*p++ = 'x';
}
}
}
j++;
}
}
}
static
int parse_args(int argc, char **argv)
{
const char *command;
if (argc < 2) {
fprintf(stderr, "Command isn't specified. "
"Supported commands are put and get\n");
usage();
exit(EXIT_FAILURE);
}
command = argv[1];
argc--; argv++;
if (strcasecmp(command, "put") == 0) {
opt_mode = MODE_PUT;
} else if (strcasecmp(command, "get") == 0) {
opt_mode = MODE_GET;
} else if (strcasecmp(command, "delete") == 0) {
opt_mode = MODE_DELETE;
} else {
fprintf(stderr, "Unknown command %s. "
"Supported commands are put and get\n", command);
usage();
exit(EXIT_FAILURE);
}
load_defaults_or_exit("my", load_default_groups, &argc, &argv);
if (handle_options(&argc, &argv, my_long_options, get_one_option)) {
exit(EXIT_FAILURE);
}
/* make sure name is specified */
if (argc < 1) {
fprintf(stderr, "Backup name is required argument\n");
exit(EXIT_FAILURE);
}
opt_name = argv[0];
argc--; argv++;
/* validate arguments */
if (opt_storage == SWIFT) {
if (opt_swift_user == NULL) {
fprintf(stderr, "Swift user is not specified\n");
exit(EXIT_FAILURE);
}
if (opt_swift_container == NULL) {
fprintf(stderr,
"Swift container is not specified\n");
exit(EXIT_FAILURE);
}
if (opt_swift_auth_url == NULL) {
fprintf(stderr, "Swift auth URL is not specified\n");
exit(EXIT_FAILURE);
}
} else {
fprintf(stderr, "Swift is only supported storage API\n");
}
if (argc > 0) {
file_list = argv;
file_list_size = argc;
}
return(0);
}
static char *hex_md5(const unsigned char *hash, char *out)
{
enum { hash_len = 16 };
char *p;
int i;
for (i = 0, p = out; i < hash_len; i++, p+=2) {
sprintf(p, "%02x", hash[i]);
}
return out;
}
/* If header starts with prefix it's value will be copied into output buffer */
static
int get_http_header(const char *prefix, const char *buffer,
char *out, size_t out_size)
{
const char *beg, *end;
size_t len, prefix_len;
prefix_len = strlen(prefix);
if (strncasecmp(buffer, prefix, prefix_len) == 0) {
beg = buffer + prefix_len;
end = strchr(beg, '\r');
len = min<size_t>(end - beg, out_size - 1);
strncpy(out, beg, len);
out[len] = 0;
return 1;
}
return 0;
}
static
size_t swift_auth_header_read_cb(char *ptr, size_t size, size_t nmemb,
void *data)
{
swift_auth_info *info = (swift_auth_info*)(data);
get_http_header("X-Storage-Url: ", ptr,
info->url, array_elements(info->url));
get_http_header("X-Auth-Token: ", ptr,
info->token, array_elements(info->token));
return nmemb * size;
}
/*********************************************************************//**
Authenticate against Swift TempAuth. Fills swift_auth_info struct.
Uses creadentials privided as global variables.
@returns true if access is granted and token received. */
static
bool
swift_temp_auth(const char *auth_url, swift_auth_info *info)
{
CURL *curl;
CURLcode res;
long http_code;
char *hdr_buf = NULL;
struct curl_slist *slist = NULL;
if (opt_swift_user == NULL) {
fprintf(stderr, "Swift user must be specified for TempAuth.\n");
return(false);
}
if (opt_swift_key == NULL) {
fprintf(stderr, "Swift key must be specified for TempAuth.\n");
return(false);
}
curl = curl_easy_init();
if (curl != NULL) {
hdr_buf = (char *)(calloc(14 + max(strlen(opt_swift_user),
strlen(opt_swift_key)), 1));
if (!hdr_buf) {
res = CURLE_FAILED_INIT;
goto cleanup;
}
sprintf(hdr_buf, "X-Auth-User: %s", opt_swift_user);
slist = curl_slist_append(slist, hdr_buf);
sprintf(hdr_buf, "X-Auth-Key: %s", opt_swift_key);
slist = curl_slist_append(slist, hdr_buf);
curl_easy_setopt(curl, CURLOPT_VERBOSE, opt_verbose);
curl_easy_setopt(curl, CURLOPT_URL, auth_url);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION,
swift_auth_header_read_cb);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, info);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
if (opt_cacert != NULL)
curl_easy_setopt(curl, CURLOPT_CAINFO, opt_cacert);
if (opt_insecure)
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE);
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr, "error: authentication failed: "
"curl_easy_perform(): %s\n",
curl_easy_strerror(res));
goto cleanup;
}
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
if (http_code != 200 &&
http_code != 204) {
fprintf(stderr, "error: authentication failed "
"with response code: %ld\n", http_code);
res = CURLE_LOGIN_DENIED;
goto cleanup;
}
} else {
res = CURLE_FAILED_INIT;
fprintf(stderr, "error: curl_easy_init() failed\n");
goto cleanup;
}
cleanup:
if (hdr_buf) {
free(hdr_buf);
}
if (slist) {
curl_slist_free_all(slist);
}
if (curl) {
curl_easy_cleanup(curl);
}
if (res == CURLE_OK) {
/* check that we received token and storage URL */
if (*info->url == 0) {
fprintf(stderr, "error: malformed response: "
"X-Storage-Url is missing\n");
return(false);
}
if (*info->token == 0) {
fprintf(stderr, "error: malformed response: "
"X-Auth-Token is missing\n");
return(false);
}
return(true);
}
return(false);
}
static
size_t
write_null_cb(char *buffer, size_t size, size_t nmemb, void *stream)
{
return fwrite(buffer, size, nmemb, stderr);
}
static
size_t
read_null_cb(char *ptr, size_t size, size_t nmemb, void *data)
{
return 0;
}
static
int
swift_create_container(swift_auth_info *info, const char *name)
{
char url[SWIFT_MAX_URL_SIZE];
char auth_token[SWIFT_MAX_HDR_SIZE];
CURLcode res;
long http_code;
CURL *curl;
struct curl_slist *slist = NULL;
snprintf(url, array_elements(url), "%s/%s", info->url, name);
snprintf(auth_token, array_elements(auth_token), "X-Auth-Token: %s",
info->token);
curl = curl_easy_init();
if (curl != NULL) {
slist = curl_slist_append(slist, auth_token);
slist = curl_slist_append(slist, "Content-Length: 0");
curl_easy_setopt(curl, CURLOPT_VERBOSE, opt_verbose);
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_null_cb);
curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_null_cb);
curl_easy_setopt(curl, CURLOPT_INFILESIZE, 0L);
curl_easy_setopt(curl, CURLOPT_PUT, 1L);
if (opt_cacert != NULL)
curl_easy_setopt(curl, CURLOPT_CAINFO, opt_cacert);
if (opt_insecure)
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE);
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr,
"error: curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
goto cleanup;
}
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
if (http_code != 201 && /* created */
http_code != 202 /* accepted (already exists) */) {
fprintf(stderr, "error: request failed "
"with response code: %ld\n", http_code);
res = CURLE_LOGIN_DENIED;
goto cleanup;
}
} else {
res = CURLE_FAILED_INIT;
fprintf(stderr, "error: curl_easy_init() failed\n");
goto cleanup;
}
cleanup:
if (slist) {
curl_slist_free_all(slist);
}
if (curl) {
curl_easy_cleanup(curl);
}
return res;
}
/*********************************************************************//**
Delete object with given url.
@returns true if object deleted successfully. */
static
bool
swift_delete_object(swift_auth_info *info, const char *url)
{
char auth_token[SWIFT_MAX_HDR_SIZE];
CURLcode res;
long http_code;
CURL *curl;
struct curl_slist *slist = NULL;
bool ret = false;
snprintf(auth_token, array_elements(auth_token), "X-Auth-Token: %s",
info->token);
curl = curl_easy_init();
if (curl != NULL) {
slist = curl_slist_append(slist, auth_token);
curl_easy_setopt(curl, CURLOPT_VERBOSE, opt_verbose);
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
if (opt_cacert != NULL)
curl_easy_setopt(curl, CURLOPT_CAINFO, opt_cacert);
if (opt_insecure)
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE);
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr,
"error: curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
goto cleanup;
}
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
if (http_code != 200 && /* OK */
http_code != 204 /* no content */) {
fprintf(stderr, "error: request failed "
"with response code: %ld\n", http_code);
goto cleanup;
}
ret = true;
} else {
fprintf(stderr, "error: curl_easy_init() failed\n");
goto cleanup;
}
cleanup:
if (slist) {
curl_slist_free_all(slist);
}
if (curl) {
curl_easy_cleanup(curl);
}
return ret;
}
static int conn_upload_init(connection_info *conn);
static void conn_buffer_updated(connection_info *conn);
static connection_info *conn_new(global_io_info *global, ulong global_idx);
static void conn_cleanup(connection_info *conn);
static void conn_upload_retry(connection_info *conn);
/* Check for completed transfers, and remove their easy handles */
static void check_multi_info(global_io_info *g)
{
char *eff_url;
CURLMsg *msg;
int msgs_left;
connection_info *conn;
CURL *easy;
while ((msg = curl_multi_info_read(g->multi, &msgs_left))) {
if (msg->msg == CURLMSG_DONE) {
easy = msg->easy_handle;
curl_easy_getinfo(easy, CURLINFO_PRIVATE, &conn);
curl_easy_getinfo(easy, CURLINFO_EFFECTIVE_URL,
&eff_url);
curl_multi_remove_handle(g->multi, easy);
curl_easy_cleanup(easy);
conn->easy = NULL;
if (conn->chunk_acked) {
conn->chunk_uploaded = true;
fprintf(stderr, "%s is done\n", conn->hash);
} else {
fprintf(stderr, "error: chunk %zu '%s' %s "
"is not uploaded, but socket closed "
"(%zu bytes of %zu left to upload)\n",
conn->chunk_no,
conn->name,
conn->hash,
conn->chunk_size - conn->upload_size,
conn->chunk_size);
conn_upload_retry(conn);
}
}
}
}
/* Die if we get a bad CURLMcode somewhere */
static void mcode_or_die(const char *where, CURLMcode code)
{
if (code != CURLM_OK)
{
const char *s;
switch (code)
{
case CURLM_BAD_HANDLE:
s = "CURLM_BAD_HANDLE";
break;
case CURLM_BAD_EASY_HANDLE:
s = "CURLM_BAD_EASY_HANDLE";
break;
case CURLM_OUT_OF_MEMORY:
s = "CURLM_OUT_OF_MEMORY";
break;
case CURLM_INTERNAL_ERROR:
s = "CURLM_INTERNAL_ERROR";
break;
case CURLM_UNKNOWN_OPTION:
s = "CURLM_UNKNOWN_OPTION";
break;
case CURLM_LAST:
s = "CURLM_LAST";
break;
default:
s = "CURLM_unknown";
break;
case CURLM_BAD_SOCKET:
s = "CURLM_BAD_SOCKET";
fprintf(stderr, "error: %s returns (%d) %s\n",
where, code, s);
/* ignore this error */
return;
}
fprintf(stderr, "error: %s returns (%d) %s\n",
where, code, s);
assert(0);
}
}
/* Called by libev when we get action on a multi socket */
static void event_cb(EV_P_ struct ev_io *w, int revents)
{
global_io_info *global = (global_io_info*)(w->data);
CURLMcode rc;
#if !(OLD_CURL_MULTI)
int action = (revents & EV_READ ? CURL_POLL_IN : 0) |
(revents & EV_WRITE ? CURL_POLL_OUT : 0);
do {
rc = curl_multi_socket_action(global->multi, w->fd, action,
&global->still_running);
} while (rc == CURLM_CALL_MULTI_PERFORM);
#else
do {
rc = curl_multi_socket(global->multi, w->fd,
&global->still_running);
} while (rc == CURLM_CALL_MULTI_PERFORM);
#endif
mcode_or_die("error: event_cb: curl_multi_socket_action", rc);
check_multi_info(global);
if (global->still_running <= 0) {
ev_timer_stop(global->loop, &global->timer_event);
}
}
static void remsock(curl_socket_t s, socket_info *fdp, global_io_info *global)
{
if (fdp) {
if (fdp->evset) {
ev_io_stop(global->loop, &fdp->ev);
}
free(fdp);
}
}
static void setsock(socket_info *fdp, curl_socket_t s, CURL *easy, int action,
global_io_info *global)
{
int kind = (action & CURL_POLL_IN ? (int)(EV_READ) : 0) |
(action & CURL_POLL_OUT ? (int)(EV_WRITE) : 0);
fdp->sockfd = s;
fdp->action = action;
fdp->easy = easy;
if (fdp->evset)
ev_io_stop(global->loop, &fdp->ev);
ev_io_init(&fdp->ev, event_cb, fdp->sockfd, kind);
fdp->ev.data = global;
fdp->evset = 1;
ev_io_start(global->loop, &fdp->ev);
}
static void addsock(curl_socket_t s, CURL *easy, int action,
global_io_info *global)
{
socket_info *fdp = (socket_info *)(calloc(sizeof(socket_info), 1));
fdp->global = global;
setsock(fdp, s, easy, action, global);
curl_multi_assign(global->multi, s, fdp);
}
static int sock_cb(CURL *easy, curl_socket_t s, int what, void *cbp,
void *sockp)
{
global_io_info *global = (global_io_info*)(cbp);
socket_info *fdp = (socket_info*)(sockp);
if (what == CURL_POLL_REMOVE) {
remsock(s, fdp, global);
} else {
if (!fdp) {
addsock(s, easy, what, global);
} else {
setsock(fdp, s, easy, what, global);
}
}
return 0;
}
/* Called by libev when our timeout expires */
static void timer_cb(EV_P_ struct ev_timer *w, int revents)
{
global_io_info *io_global = (global_io_info*)(w->data);
CURLMcode rc;
#if !(OLD_CURL_MULTI)
do {
rc = curl_multi_socket_action(io_global->multi,
CURL_SOCKET_TIMEOUT, 0,
&io_global->still_running);
} while (rc == CURLM_CALL_MULTI_PERFORM);
#else
do {
rc = curl_multi_socket_all(io_global->multi,
&io_global->still_running);
} while (rc == CURLM_CALL_MULTI_PERFORM);
#endif
mcode_or_die("timer_cb: curl_multi_socket_action", rc);
check_multi_info(io_global);
}
static connection_info *get_current_connection(global_io_info *global)
{
connection_info *conn = global->current_connection;
ulong i;
if (conn && conn->filled_size < conn->chunk_size)
return conn;
for (i = 0; i < opt_parallel; i++) {
conn = global->connections[i];
if (conn->chunk_uploaded || conn->filled_size == 0) {
global->current_connection = conn;
conn_upload_init(conn);
return conn;
}
}
return NULL;
}
/* This gets called whenever data is received from the input */
static void input_cb(EV_P_ struct ev_io *w, int revents)
{
global_io_info *io_global = (global_io_info *)(w->data);
connection_info *conn = get_current_connection(io_global);
if (conn == NULL)
return;
if (conn->filled_size < conn->chunk_size) {
if (revents & EV_READ) {
ssize_t nbytes = read(io_global->input_fd,
conn->buffer + conn->filled_size,
conn->chunk_size -
conn->filled_size);
if (nbytes > 0) {
conn->filled_size += nbytes;
conn_buffer_updated(conn);
} else if (nbytes < 0) {
if (errno != EAGAIN && errno != EINTR) {
char error[200];
my_strerror(error, sizeof(error),
errno);
fprintf(stderr, "error: failed to read "
"input stream (%s)\n", error);
/* failed to read input */
exit(1);
}
} else {
io_global->eof = 1;
ev_io_stop(io_global->loop, w);
}
}
}
assert(conn->filled_size <= conn->chunk_size);
}
static int swift_upload_read_cb(char *ptr, size_t size, size_t nmemb,
void *data)
{
size_t realsize;
connection_info *conn = (connection_info*)(data);
if (conn->filled_size == conn->upload_size &&
conn->upload_size < conn->chunk_size && !conn->global->eof) {
ssize_t nbytes;
assert(conn->global->current_connection == conn);
do {
nbytes = read(conn->global->input_fd,
conn->buffer + conn->filled_size,
conn->chunk_size - conn->filled_size);
} while (nbytes == -1 && errno == EAGAIN);
if (nbytes > 0) {
conn->filled_size += nbytes;
conn_buffer_updated(conn);
} else {
conn->global->eof = 1;
}
}
realsize = min(size * nmemb, conn->filled_size - conn->upload_size);
memcpy(ptr, conn->buffer + conn->upload_size, realsize);
conn->upload_size += realsize;
assert(conn->filled_size <= conn->chunk_size);
assert(conn->upload_size <= conn->filled_size);
return realsize;
}
static
size_t upload_header_read_cb(char *ptr, size_t size, size_t nmemb,
void *data)
{
connection_info *conn = (connection_info *)(data);
char etag[33];
if (get_http_header("Etag: ", ptr, etag, array_elements(etag))) {
if (strcmp(conn->hash, etag) != 0) {
fprintf(stderr, "error: ETag mismatch\n");
exit(EXIT_FAILURE);
}
fprintf(stderr, "acked chunk %s\n", etag);
conn->chunk_acked = true;
}
return nmemb * size;
}
static int conn_upload_init(connection_info *conn)
{
conn->filled_size = 0;
conn->upload_size = 0;
conn->chunk_uploaded = false;
conn->chunk_acked = false;
conn->chunk_size = CHUNK_HEADER_CONSTANT_LEN;
conn->magic_verified = false;
conn->chunk_path_len = 0;
conn->chunk_type = XB_CHUNK_TYPE_UNKNOWN;
conn->payload_size = 0;
conn->upload_started = false;
conn->retry_count = 0;
if (conn->name != NULL) {
conn->name[0] = 0;
}
if (conn->easy != NULL) {
conn->easy = 0;
}
if (conn->slist != NULL) {
curl_slist_free_all(conn->slist);
conn->slist = NULL;
}
return 0;
}
static void conn_upload_prepare(connection_info *conn)
{
gcry_md_hd_t md5;
gcry_md_open(&md5, GCRY_MD_MD5, 0);
gcry_md_write(md5, conn->buffer, conn->chunk_size);
hex_md5(gcry_md_read(md5, GCRY_MD_MD5), conn->hash);
gcry_md_close(md5);
}
static int conn_upload_start(connection_info *conn)
{
char token_header[SWIFT_MAX_HDR_SIZE];
char object_url[SWIFT_MAX_URL_SIZE];
char content_len[200], etag[200];
global_io_info *global;
CURLMcode rc;
global = conn->global;
fprintf(stderr, "uploading chunk %s/%s/%s.%020zu "
"(md5: %s, size: %zu)\n",
global->container, global->backup_name, conn->name,
conn->chunk_no, conn->hash, conn->chunk_size);
snprintf(object_url, array_elements(object_url), "%s/%s/%s/%s.%020zu",
global->url, global->container, global->backup_name,
conn->name, conn->chunk_no);
snprintf(content_len, sizeof(content_len), "Content-Length: %lu",
(ulong)(conn->chunk_size));
snprintf(etag, sizeof(etag), "ETag: %s", conn->hash);
snprintf(token_header, array_elements(token_header),
"X-Auth-Token: %s", global->token);
conn->slist = curl_slist_append(conn->slist, token_header);
conn->slist = curl_slist_append(conn->slist,
"Connection: keep-alive");
conn->slist = curl_slist_append(conn->slist,
"Content-Type: "
"application/octet-stream");
conn->slist = curl_slist_append(conn->slist, content_len);
conn->slist = curl_slist_append(conn->slist, etag);
conn->easy = curl_easy_init();
if (!conn->easy) {
fprintf(stderr, "error: curl_easy_init() failed\n");
return 1;
}
curl_easy_setopt(conn->easy, CURLOPT_URL, object_url);
curl_easy_setopt(conn->easy, CURLOPT_READFUNCTION,
swift_upload_read_cb);
curl_easy_setopt(conn->easy, CURLOPT_READDATA, conn);
curl_easy_setopt(conn->easy, CURLOPT_VERBOSE, opt_verbose);
curl_easy_setopt(conn->easy, CURLOPT_ERRORBUFFER, conn->error);
curl_easy_setopt(conn->easy, CURLOPT_PRIVATE, conn);
curl_easy_setopt(conn->easy, CURLOPT_NOPROGRESS, 1L);
curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_TIME, 5L);
curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_LIMIT, 1024L);
curl_easy_setopt(conn->easy, CURLOPT_PUT, 1L);
curl_easy_setopt(conn->easy, CURLOPT_HTTPHEADER, conn->slist);
curl_easy_setopt(conn->easy, CURLOPT_HEADERFUNCTION,
upload_header_read_cb);
curl_easy_setopt(conn->easy, CURLOPT_HEADERDATA, conn);
curl_easy_setopt(conn->easy, CURLOPT_INFILESIZE,
(long) conn->chunk_size);
if (opt_cacert != NULL)
curl_easy_setopt(conn->easy, CURLOPT_CAINFO, opt_cacert);
if (opt_insecure)
curl_easy_setopt(conn->easy, CURLOPT_SSL_VERIFYPEER, FALSE);
rc = curl_multi_add_handle(conn->global->multi, conn->easy);
mcode_or_die("conn_upload_init: curl_multi_add_handle", rc);
#if (OLD_CURL_MULTI)
do {
rc = curl_multi_socket_all(global->multi,
&global->still_running);
} while(rc == CURLM_CALL_MULTI_PERFORM);
#endif
conn->upload_started = true;
return 0;
}
static void conn_cleanup(connection_info *conn)
{
if (conn) {
free(conn->name);
free(conn->buffer);
if (conn->slist) {
curl_slist_free_all(conn->slist);
conn->slist = NULL;
}
if (conn->easy) {
curl_easy_cleanup(conn->easy);
conn->easy = NULL;
}
}
free(conn);
}
static void conn_upload_retry(connection_info *conn)
{
/* already closed by cURL */
conn->easy = NULL;
if (conn->slist != NULL) {
curl_slist_free_all(conn->slist);
conn->slist = NULL;
}
if (conn->retry_count++ > 3) {
fprintf(stderr, "error: retry count limit reached\n");
exit(EXIT_FAILURE);
}
fprintf(stderr, "warning: retrying to upload chunk %zu of '%s'\n",
conn->chunk_no, conn->name);
conn->upload_size = 0;
conn_upload_start(conn);
}
static connection_info *conn_new(global_io_info *global, ulong global_idx)
{
connection_info *conn;
conn = (connection_info *)(calloc(1, sizeof(connection_info)));
if (conn == NULL) {
goto error;
}
conn->global = global;
conn->global_idx = global_idx;
conn->buffer_size = SWIFT_CHUNK_SIZE;
if ((conn->buffer = (char *)(calloc(conn->buffer_size, 1))) ==
NULL) {
goto error;
}
return conn;
error:
if (conn != NULL) {
conn_cleanup(conn);
}
fprintf(stderr, "error: out of memory\n");
exit(EXIT_FAILURE);
return NULL;
}
/*********************************************************************//**
Handle input buffer updates. Parse chunk header and set appropriate
buffer size. */
static
void
conn_buffer_updated(connection_info *conn)
{
bool ready_for_upload = false;
/* chunk header */
if (!conn->magic_verified &&
conn->filled_size >= CHUNK_HEADER_CONSTANT_LEN) {
if (strncmp(XB_STREAM_CHUNK_MAGIC, conn->buffer,
sizeof(XB_STREAM_CHUNK_MAGIC) - 1) != 0) {
fprintf(stderr, "Error: magic expected\n");
exit(EXIT_FAILURE);
}
conn->magic_verified = true;
conn->chunk_path_len = uint4korr(conn->buffer
+ PATH_LENGTH_OFFSET);
conn->chunk_type = (xb_chunk_type_t)
(conn->buffer[CHUNK_TYPE_OFFSET]);
conn->chunk_size = CHUNK_HEADER_CONSTANT_LEN +
conn->chunk_path_len;
if (conn->chunk_type != XB_CHUNK_TYPE_EOF) {
conn->chunk_size += 16;
}
}
/* ordinary chunk */
if (conn->magic_verified &&
conn->payload_size == 0 &&
conn->chunk_type != XB_CHUNK_TYPE_EOF &&
conn->filled_size >= CHUNK_HEADER_CONSTANT_LEN
+ conn->chunk_path_len + 16) {
conn->payload_size = uint8korr(conn->buffer +
CHUNK_HEADER_CONSTANT_LEN +
conn->chunk_path_len);
conn->chunk_size = conn->payload_size + 4 + 16 +
conn->chunk_path_len +
CHUNK_HEADER_CONSTANT_LEN;
if (conn->name == NULL) {
conn->name = (char*)(malloc(conn->chunk_path_len + 1));
} else if (conn->name_len < conn->chunk_path_len + 1) {
conn->name = (char*)(realloc(conn->name,
conn->chunk_path_len + 1));
}
conn->name_len = conn->chunk_path_len + 1;
memcpy(conn->name, conn->buffer + CHUNK_HEADER_CONSTANT_LEN,
conn->chunk_path_len);
conn->name[conn->chunk_path_len] = 0;
if (conn->buffer_size < conn->chunk_size) {
conn->buffer =
(char *)(realloc(conn->buffer, conn->chunk_size));
conn->buffer_size = conn->chunk_size;
}
}
/* EOF chunk has no payload */
if (conn->magic_verified &&
conn->chunk_type == XB_CHUNK_TYPE_EOF &&
conn->filled_size >= CHUNK_HEADER_CONSTANT_LEN
+ conn->chunk_path_len) {
if (conn->name == NULL) {
conn->name = (char*)(malloc(conn->chunk_path_len + 1));
} else if (conn->name_len < conn->chunk_path_len + 1) {
conn->name = (char*)(realloc(conn->name,
conn->chunk_path_len + 1));
}
conn->name_len = conn->chunk_path_len + 1;
memcpy(conn->name, conn->buffer + CHUNK_HEADER_CONSTANT_LEN,
conn->chunk_path_len);
conn->name[conn->chunk_path_len] = 0;
}
if (conn->filled_size > 0 && conn->filled_size == conn->chunk_size) {
ready_for_upload = true;
}
/* start upload once recieved the size of the chunk */
if (!conn->upload_started && ready_for_upload) {
conn->chunk_no = file_chunk_count[conn->name]++;
conn_upload_prepare(conn);
conn_upload_start(conn);
}
}
static int init_input(global_io_info *io_global)
{
ev_io_init(&io_global->input_event, input_cb, STDIN_FILENO, EV_READ);
io_global->input_event.data = io_global;
ev_io_start(io_global->loop, &io_global->input_event);
return 0;
}
/* Update the event timer after curl_multi library calls */
static int multi_timer_cb(CURLM *multi, long timeout_ms, global_io_info *global)
{
ev_timer_stop(global->loop, &global->timer_event);
if (timeout_ms > 0) {
double t = timeout_ms / 1000.0;
ev_timer_init(&global->timer_event, timer_cb, t, 0.);
ev_timer_start(global->loop, &global->timer_event);
} else {
timer_cb(global->loop, &global->timer_event, 0);
}
return 0;
}
static
int swift_upload_parts(swift_auth_info *auth, const char *container,
const char *name)
{
global_io_info io_global;
ulong i;
#if (OLD_CURL_MULTI)
long timeout;
#endif
CURLMcode rc;
int n_dirty_buffers;
memset(&io_global, 0, sizeof(io_global));
io_global.loop = ev_default_loop(0);
init_input(&io_global);
io_global.multi = curl_multi_init();
ev_timer_init(&io_global.timer_event, timer_cb, 0., 0.);
io_global.timer_event.data = &io_global;
io_global.connections = (connection_info **)
(calloc(opt_parallel, sizeof(connection_info)));
io_global.url = auth->url;
io_global.container = container;
io_global.backup_name = name;
io_global.token = auth->token;
for (i = 0; i < opt_parallel; i++) {
io_global.connections[i] = conn_new(&io_global, i);
}
/* setup the generic multi interface options we want */
curl_multi_setopt(io_global.multi, CURLMOPT_SOCKETFUNCTION, sock_cb);
curl_multi_setopt(io_global.multi, CURLMOPT_SOCKETDATA, &io_global);
#if !(OLD_CURL_MULTI)
curl_multi_setopt(io_global.multi, CURLMOPT_TIMERFUNCTION, multi_timer_cb);
curl_multi_setopt(io_global.multi, CURLMOPT_TIMERDATA, &io_global);
do {
rc = curl_multi_socket_action(io_global.multi,
CURL_SOCKET_TIMEOUT, 0,
&io_global.still_running);
} while (rc == CURLM_CALL_MULTI_PERFORM);
#else
curl_multi_timeout(io_global.multi, &timeout);
if (timeout >= 0) {
multi_timer_cb(io_global.multi, timeout, &io_global);
}
do {
rc = curl_multi_socket_all(io_global.multi, &io_global.still_running);
} while(rc == CURLM_CALL_MULTI_PERFORM);
#endif
ev_loop(io_global.loop, 0);
check_multi_info(&io_global);
curl_multi_cleanup(io_global.multi);
n_dirty_buffers = 0;
for (i = 0; i < opt_parallel; i++) {
connection_info *conn = io_global.connections[i];
if (conn && conn->upload_size != conn->filled_size) {
fprintf(stderr, "error: upload failed: %lu bytes left "
"in the buffer %s (uploaded = %d)\n",
(ulong)(conn->filled_size - conn->upload_size),
conn->name, conn->chunk_uploaded);
++n_dirty_buffers;
}
}
for (i = 0; i < opt_parallel; i++) {
if (io_global.connections[i] != NULL) {
conn_cleanup(io_global.connections[i]);
}
}
free(io_global.connections);
if (n_dirty_buffers > 0) {
return(EXIT_FAILURE);
}
return 0;
}
struct download_buffer_info {
off_t offset;
size_t size;
size_t result_len;
char *buf;
curl_read_callback custom_header_callback;
void *custom_header_callback_data;
};
/*********************************************************************//**
Callback to parse header of GET request on swift contaier. */
static
size_t fetch_buffer_header_cb(char *ptr, size_t size, size_t nmemb,
void *data)
{
download_buffer_info *buffer_info = (download_buffer_info*)(data);
size_t buf_size;
char content_length_str[100];
char *endptr;
if (get_http_header("Content-Length: ", ptr,
content_length_str, sizeof(content_length_str))) {
buf_size = strtoull(content_length_str, &endptr, 10);
if (buffer_info->buf == NULL) {
buffer_info->buf = (char*)(malloc(buf_size));
buffer_info->size = buf_size;
}
if (buf_size > buffer_info->size) {
buffer_info->buf = (char*)
(realloc(buffer_info->buf, buf_size));
buffer_info->size = buf_size;
}
buffer_info->result_len = buf_size;
}
if (buffer_info->custom_header_callback) {
buffer_info->custom_header_callback(ptr, size, nmemb,
buffer_info->custom_header_callback_data);
}
return nmemb * size;
}
/*********************************************************************//**
Write contents into string buffer */
static
size_t
fetch_buffer_cb(char *buffer, size_t size, size_t nmemb, void *out_buffer)
{
download_buffer_info *buffer_info = (download_buffer_info*)(out_buffer);
assert(buffer_info->size >= buffer_info->offset + size * nmemb);
memcpy(buffer_info->buf + buffer_info->offset, buffer, size * nmemb);
buffer_info->offset += size * nmemb;
return size * nmemb;
}
/*********************************************************************//**
Downloads contents of URL into buffer. Caller is responsible for
deallocating the buffer.
@return pointer to a buffer or NULL */
static
char *
swift_fetch_into_buffer(swift_auth_info *auth, const char *url,
char **buf, size_t *buf_size, size_t *result_len,
curl_read_callback header_callback,
void *header_callback_data)
{
char auth_token[SWIFT_MAX_HDR_SIZE];
download_buffer_info buffer_info;
struct curl_slist *slist = NULL;
long http_code;
CURL *curl;
CURLcode res;
memset(&buffer_info, 0, sizeof(buffer_info));
buffer_info.buf = *buf;
buffer_info.size = *buf_size;
buffer_info.custom_header_callback = header_callback;
buffer_info.custom_header_callback_data = header_callback_data;
snprintf(auth_token, array_elements(auth_token), "X-Auth-Token: %s",
auth->token);
curl = curl_easy_init();
if (curl != NULL) {
slist = curl_slist_append(slist, auth_token);
curl_easy_setopt(curl, CURLOPT_VERBOSE, opt_verbose);
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fetch_buffer_cb);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer_info);
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION,
fetch_buffer_header_cb);
curl_easy_setopt(curl, CURLOPT_HEADERDATA,
&buffer_info);
if (opt_cacert != NULL)
curl_easy_setopt(curl, CURLOPT_CAINFO, opt_cacert);
if (opt_insecure)
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE);
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr,
"error: curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
goto cleanup;
}
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
if (http_code < 200 || http_code >= 300) {
fprintf(stderr, "error: request failed "
"with response code: %ld\n", http_code);
res = CURLE_LOGIN_DENIED;
goto cleanup;
}
} else {
res = CURLE_FAILED_INIT;
fprintf(stderr, "error: curl_easy_init() failed\n");
goto cleanup;
}
cleanup:
if (slist) {
curl_slist_free_all(slist);
}
if (curl) {
curl_easy_cleanup(curl);
}
if (res == CURLE_OK) {
*buf = buffer_info.buf;
*buf_size = buffer_info.size;
*result_len = buffer_info.result_len;
return(buffer_info.buf);
}
free(buffer_info.buf);
*buf = NULL;
*buf_size = 0;
*result_len = 0;
return(NULL);
}
static
container_list *
container_list_new()
{
container_list *list =
(container_list *)(calloc(1, sizeof(container_list)));
list->object_count = 1000;
list->objects = (object_info*)
(calloc(list->object_count, sizeof(object_info)));
if (list->objects == NULL) {
fprintf(stderr, "error: out of memory\n");
free(list);
return(NULL);
}
return(list);
}
static
void
container_list_free(container_list *list)
{
free(list->content_json);
free(list->objects);
free(list);
}
static
void
container_list_add_object(container_list *list, const char *name,
const char *hash, size_t bytes)
{
const size_t object_count_step = 1000;
if (list->idx >= list->object_count) {
list->objects = (object_info*)
realloc(list->objects,
(list->object_count + object_count_step) *
sizeof(object_info));
memset(list->objects + list->object_count, 0,
object_count_step * sizeof(object_info));
list->object_count += object_count_step;
}
assert(list->idx <= list->object_count);
safe_strcpy(list->objects[list->idx].name,
sizeof(list->objects[list->idx].name), name);
safe_strcpy(list->objects[list->idx].hash,
sizeof(list->objects[list->idx].hash), hash);
list->objects[list->idx].bytes = bytes;
++list->idx;
}
/*********************************************************************//**
Tokenize json string. Return array of tokens. Caller is responsoble for
deallocating the array. */
jsmntok_t *
json_tokenise(char *json, size_t len, int initial_tokens)
{
jsmn_parser parser;
jsmn_init(&parser);
unsigned int n = initial_tokens;
jsmntok_t *tokens = (jsmntok_t *)(malloc(sizeof(jsmntok_t) * n));
int ret = jsmn_parse(&parser, json, len, tokens, n);
while (ret == JSMN_ERROR_NOMEM)
{
n = n * 2 + 1;
tokens = (jsmntok_t*)(realloc(tokens, sizeof(jsmntok_t) * n));
ret = jsmn_parse(&parser, json, len, tokens, n);
}
if (ret == JSMN_ERROR_INVAL) {
fprintf(stderr, "error: invalid JSON string\n");
}
if (ret == JSMN_ERROR_PART) {
fprintf(stderr, "error: truncated JSON string\n");
}
return tokens;
}
/*********************************************************************//**
Return true if token representation equal to given string. */
static
bool
json_token_eq(const char *buf, jsmntok_t *t, const char *s)
{
size_t len = strlen(s);
assert(t->end > t->start);
return((size_t)(t->end - t->start) == len &&
(strncmp(buf + t->start, s, len) == 0));
}
/*********************************************************************//**
Copy given token as string. */
static
bool
json_token_str(const char *buf, jsmntok_t *t, char *out, int out_size)
{
size_t len = min(t->end - t->start, out_size - 1);
memcpy(out, buf + t->start, len);
out[len] = 0;
return(true);
}
/*********************************************************************//**
Parse SWIFT container list response and fill output array with values
sorted by object name. */
static
bool
swift_parse_container_list(container_list *list)
{
enum {MAX_DEPTH=20};
enum label_t {NONE, OBJECT};
char name[SWIFT_MAX_URL_SIZE];
char hash[33];
char bytes[30];
char *response = list->content_json;
struct stack_t {
jsmntok_t *t;
int n_items;
label_t label;
};
stack_t stack[MAX_DEPTH];
jsmntok_t *tokens;
int level;
size_t count = 0;
tokens = json_tokenise(list->content_json, list->content_length, 200);
stack[0].t = &tokens[0];
stack[0].label = NONE;
stack[0].n_items = 1;
level = 0;
for (size_t i = 0, j = 1; j > 0; i++, j--) {
jsmntok_t *t = &tokens[i];
assert(t->start != -1 && t->end != -1);
assert(level >= 0);
--stack[level].n_items;
switch (t->type) {
case JSMN_ARRAY:
case JSMN_OBJECT:
if (level < MAX_DEPTH - 1) {
level++;
}
stack[level].t = t;
stack[level].label = NONE;
if (t->type == JSMN_ARRAY) {
stack[level].n_items = t->size;
j += t->size;
} else {
stack[level].n_items = t->size * 2;
j += t->size * 2;
}
break;
case JSMN_PRIMITIVE:
case JSMN_STRING:
if (stack[level].t->type == JSMN_OBJECT &&
stack[level].n_items % 2 == 1) {
/* key */
if (json_token_eq(response, t, "name")) {
json_token_str(response, &tokens[i + 1],
name, sizeof(name));
}
if (json_token_eq(response, t, "hash")) {
json_token_str(response, &tokens[i + 1],
hash, sizeof(hash));
}
if (json_token_eq(response, t, "bytes")) {
json_token_str(response, &tokens[i + 1],
bytes, sizeof(bytes));
}
}
break;
}
while (stack[level].n_items == 0 && level > 0) {
if (stack[level].t->type == JSMN_OBJECT
&& level == 2) {
char *endptr;
container_list_add_object(list, name, hash,
strtoull(bytes, &endptr, 10));
++count;
}
--level;
}
}
if (count == 0) {
list->final = true;
}
free(tokens);
return(true);
}
/*********************************************************************//**
List swift container with given name. Return list of objects sorted by
object name. */
static
container_list *
swift_list(swift_auth_info *auth, const char *container, const char *path)
{
container_list *list;
char url[SWIFT_MAX_URL_SIZE];
list = container_list_new();
while (!list->final) {
/* download the list in json format */
snprintf(url, array_elements(url),
"%s/%s?format=json&limit=1000%s%s%s%s",
auth->url, container, path ? "&prefix=" : "",
path ? path : "", list->idx > 0 ? "&marker=" : "",
list->idx > 0 ?
list->objects[list->idx - 1].name : "");
list->content_json = swift_fetch_into_buffer(auth, url,
&list->content_json, &list->content_bufsize,
&list->content_length, NULL, NULL);
if (list->content_json == NULL) {
container_list_free(list);
return(NULL);
}
/* parse downloaded list */
if (!swift_parse_container_list(list)) {
fprintf(stderr, "error: unable to parse "
"container list\n");
container_list_free(list);
return(NULL);
}
}
return(list);
}
/*********************************************************************//**
Return true if chunk is a part of backup with given name. */
static
bool
chunk_belongs_to(const char *chunk_name, const char *backup_name)
{
size_t backup_name_len = strlen(backup_name);
return((strlen(chunk_name) > backup_name_len)
&& (chunk_name[backup_name_len] == '/')
&& strncmp(chunk_name, backup_name, backup_name_len) == 0);
}
/*********************************************************************//**
Return true if chunk is in given list. */
static
bool
chunk_in_list(const char *chunk_name, char **list, int list_size)
{
size_t chunk_name_len;
if (list_size == 0) {
return(true);
}
chunk_name_len = strlen(chunk_name);
if (chunk_name_len < 20) {
return(false);
}
for (int i = 0; i < list_size; i++) {
size_t item_len = strlen(list[i]);
if ((strncmp(chunk_name - item_len + chunk_name_len - 21,
list[i], item_len) == 0)
&& (chunk_name[chunk_name_len - 21] == '.')
&& (chunk_name[chunk_name_len - item_len - 22] == '/')) {
return(true);
}
}
return(false);
}
static
int swift_download(swift_auth_info *auth, const char *container,
const char *name)
{
container_list *list;
char *buf = NULL;
size_t buf_size = 0;
size_t result_len = 0;
if ((list = swift_list(auth, container, name)) == NULL) {
return(CURLE_FAILED_INIT);
}
for (size_t i = 0; i < list->idx; i++) {
const char *chunk_name = list->objects[i].name;
if (chunk_belongs_to(chunk_name, name)
&& chunk_in_list(chunk_name, file_list, file_list_size)) {
char url[SWIFT_MAX_URL_SIZE];
snprintf(url, sizeof(url), "%s/%s/%s",
auth->url, container, chunk_name);
if ((buf = swift_fetch_into_buffer(
auth, url, &buf, &buf_size, &result_len,
NULL, NULL)) == NULL) {
fprintf(stderr, "error: failed to download "
"chunk %s\n", chunk_name);
container_list_free(list);
return(CURLE_FAILED_INIT);
}
fwrite(buf, 1, result_len, stdout);
}
}
free(buf);
container_list_free(list);
return(CURLE_OK);
}
/*********************************************************************//**
Delete backup with given name from given container.
@return true if backup deleted successfully */
static
bool swift_delete(swift_auth_info *auth, const char *container,
const char *name)
{
container_list *list;
if ((list = swift_list(auth, container, name)) == NULL) {
return(CURLE_FAILED_INIT);
}
for (size_t i = 0; i < list->object_count; i++) {
const char *chunk_name = list->objects[i].name;
if (chunk_belongs_to(chunk_name, name)) {
char url[SWIFT_MAX_URL_SIZE];
snprintf(url, sizeof(url), "%s/%s/%s",
auth->url, container, chunk_name);
fprintf(stderr, "delete %s\n", chunk_name);
if (!swift_delete_object(auth, url)) {
fprintf(stderr, "error: failed to delete "
"chunk %s\n", chunk_name);
container_list_free(list);
return(CURLE_FAILED_INIT);
}
}
}
container_list_free(list);
return(CURLE_OK);
}
/*********************************************************************//**
Check if backup with given name exists.
@return true if backup exists */
static
bool swift_backup_exists(swift_auth_info *auth, const char *container,
const char *backup_name)
{
container_list *list;
if ((list = swift_list(auth, container, backup_name)) == NULL) {
fprintf(stderr, "error: unable to list container %s\n",
container);
exit(EXIT_FAILURE);
}
for (size_t i = 0; i < list->object_count; i++) {
if (chunk_belongs_to(list->objects[i].name, backup_name)) {
container_list_free(list);
return(true);
}
}
container_list_free(list);
return(false);
}
/*********************************************************************//**
Fills auth_info with response from keystone response.
@return true is response parsed successfully */
static
bool
swift_parse_keystone_response_v2(char *response, size_t response_length,
swift_auth_info *auth_info)
{
enum {MAX_DEPTH=20};
enum label_t {NONE, ACCESS, CATALOG, ENDPOINTS, TOKEN};
char filtered_url[SWIFT_MAX_URL_SIZE];
char public_url[SWIFT_MAX_URL_SIZE];
char region[SWIFT_MAX_URL_SIZE];
char id[SWIFT_MAX_URL_SIZE];
char token_id[SWIFT_MAX_URL_SIZE];
char type[SWIFT_MAX_URL_SIZE];
struct stack_t {
jsmntok_t *t;
int n_items;
label_t label;
};
stack_t stack[MAX_DEPTH];
jsmntok_t *tokens;
int level;
tokens = json_tokenise(response, response_length, 200);
stack[0].t = &tokens[0];
stack[0].label = NONE;
stack[0].n_items = 1;
level = 0;
for (size_t i = 0, j = 1; j > 0; i++, j--) {
jsmntok_t *t = &tokens[i];
assert(t->start != -1 && t->end != -1);
assert(level >= 0);
--stack[level].n_items;
switch (t->type) {
case JSMN_ARRAY:
case JSMN_OBJECT:
if (level < MAX_DEPTH - 1) {
level++;
}
stack[level].t = t;
stack[level].label = NONE;
if (t->type == JSMN_ARRAY) {
stack[level].n_items = t->size;
j += t->size;
} else {
stack[level].n_items = t->size * 2;
j += t->size * 2;
}
break;
case JSMN_PRIMITIVE:
case JSMN_STRING:
if (stack[level].t->type == JSMN_OBJECT &&
stack[level].n_items % 2 == 1) {
/* key */
if (json_token_eq(response, t, "access")) {
stack[level].label = ACCESS;
}
if (json_token_eq(response, t,
"serviceCatalog")) {
stack[level].label = CATALOG;
}
if (json_token_eq(response, t, "endpoints")) {
stack[level].label = ENDPOINTS;
}
if (json_token_eq(response, t, "token")) {
stack[level].label = TOKEN;
}
if (json_token_eq(response, t, "id")) {
json_token_str(response, &tokens[i + 1],
id, sizeof(id));
}
if (json_token_eq(response, t, "id")
&& stack[level - 1].label == TOKEN) {
json_token_str(response, &tokens[i + 1],
token_id, sizeof(token_id));
}
if (json_token_eq(response, t, "region")) {
json_token_str(response, &tokens[i + 1],
region, sizeof(region));
}
if (json_token_eq(response, t, "publicURL")) {
json_token_str(response, &tokens[i + 1],
public_url, sizeof(public_url));
}
if (json_token_eq(response, t, "type")) {
json_token_str(response, &tokens[i + 1],
type, sizeof(type));
}
}
break;
}
while (stack[level].n_items == 0 && level > 0) {
if (stack[level].t->type == JSMN_OBJECT
&& level == 6
&& stack[level - 1].t->type == JSMN_ARRAY
&& stack[level - 2].label == ENDPOINTS) {
if (opt_swift_region == NULL
|| strcmp(opt_swift_region, region) == 0) {
strncpy(filtered_url, public_url,
sizeof(filtered_url));
}
}
if (stack[level].t->type == JSMN_OBJECT &&
level == 4 &&
stack[level - 1].t->type == JSMN_ARRAY &&
stack[level - 2].label == CATALOG) {
if (strcmp(type, "object-store") == 0) {
strncpy(auth_info->url, filtered_url,
sizeof(auth_info->url));
}
}
--level;
}
}
free(tokens);
strncpy(auth_info->token, token_id, sizeof(auth_info->token));
assert(level == 0);
if (*auth_info->token == 0) {
fprintf(stderr, "error: can not receive token from response\n");
return(false);
}
if (*auth_info->url == 0) {
fprintf(stderr, "error: can not get URL from response\n");
return(false);
}
return(true);
}
/*********************************************************************//**
Authenticate against Swift TempAuth. Fills swift_auth_info struct.
Uses creadentials privided as global variables.
@returns true if access is granted and token received. */
static
bool
swift_keystone_auth_v2(const char *auth_url, swift_auth_info *info)
{
char tenant_arg[SWIFT_MAX_URL_SIZE];
char payload[SWIFT_MAX_URL_SIZE];
struct curl_slist *slist = NULL;
download_buffer_info buf_info;
long http_code;
CURLcode res;
CURL *curl;
bool auth_res = false;
memset(&buf_info, 0, sizeof(buf_info));
if (opt_swift_user == NULL) {
fprintf(stderr, "error: both --swift-user is required "
"for keystone authentication.\n");
return(false);
}
if (opt_swift_password == NULL) {
fprintf(stderr, "error: both --swift-password is required "
"for keystone authentication.\n");
return(false);
}
if (opt_swift_tenant != NULL && opt_swift_tenant_id != NULL) {
fprintf(stderr, "error: both --swift-tenant and "
"--swift-tenant-id specified for keystone "
"authentication.\n");
return(false);
}
if (opt_swift_tenant != NULL) {
snprintf(tenant_arg, sizeof(tenant_arg), ",\"%s\":\"%s\"",
"tenantName", opt_swift_tenant);
} else if (opt_swift_tenant_id != NULL) {
snprintf(tenant_arg, sizeof(tenant_arg), ",\"%s\":\"%s\"",
"tenantId", opt_swift_tenant_id);
} else {
*tenant_arg = 0;
}
snprintf(payload, sizeof(payload), "{\"auth\": "
"{\"passwordCredentials\": {\"username\":\"%s\","
"\"password\":\"%s\"}%s}}",
opt_swift_user, opt_swift_password, tenant_arg);
curl = curl_easy_init();
if (curl != NULL) {
slist = curl_slist_append(slist,
"Content-Type: application/json");
slist = curl_slist_append(slist,
"Accept: application/json");
curl_easy_setopt(curl, CURLOPT_VERBOSE, opt_verbose);
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_URL, auth_url);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fetch_buffer_cb);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf_info);
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION,
fetch_buffer_header_cb);
curl_easy_setopt(curl, CURLOPT_HEADERDATA,
&buf_info);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
if (opt_cacert != NULL)
curl_easy_setopt(curl, CURLOPT_CAINFO, opt_cacert);
if (opt_insecure)
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE);
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr,
"error: curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
goto cleanup;
}
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
if (http_code < 200 || http_code >= 300) {
fprintf(stderr, "error: request failed "
"with response code: %ld\n", http_code);
res = CURLE_LOGIN_DENIED;
goto cleanup;
}
} else {
res = CURLE_FAILED_INIT;
fprintf(stderr, "error: curl_easy_init() failed\n");
goto cleanup;
}
if (!swift_parse_keystone_response_v2(buf_info.buf,
buf_info.size, info)) {
goto cleanup;
}
auth_res = true;
cleanup:
if (slist) {
curl_slist_free_all(slist);
}
if (curl) {
curl_easy_cleanup(curl);
}
free(buf_info.buf);
return(auth_res);
}
/*********************************************************************//**
Fills auth_info with response from keystone response.
@return true is response parsed successfully */
static
bool
swift_parse_keystone_response_v3(char *response, size_t response_length,
swift_auth_info *auth_info)
{
enum {MAX_DEPTH=20};
enum label_t {NONE, TOKEN, CATALOG, ENDPOINTS};
char url[SWIFT_MAX_URL_SIZE];
char filtered_url[SWIFT_MAX_URL_SIZE];
char region[SWIFT_MAX_URL_SIZE];
char interface[SWIFT_MAX_URL_SIZE];
char type[SWIFT_MAX_URL_SIZE];
struct stack_t {
jsmntok_t *t;
int n_items;
label_t label;
};
stack_t stack[MAX_DEPTH];
jsmntok_t *tokens;
int level;
tokens = json_tokenise(response, response_length, 200);
stack[0].t = &tokens[0];
stack[0].label = NONE;
stack[0].n_items = 1;
level = 0;
for (size_t i = 0, j = 1; j > 0; i++, j--) {
jsmntok_t *t = &tokens[i];
assert(t->start != -1 && t->end != -1);
assert(level >= 0);
--stack[level].n_items;
switch (t->type) {
case JSMN_ARRAY:
case JSMN_OBJECT:
if (level < MAX_DEPTH - 1) {
level++;
}
stack[level].t = t;
stack[level].label = NONE;
if (t->type == JSMN_ARRAY) {
stack[level].n_items = t->size;
j += t->size;
} else {
stack[level].n_items = t->size * 2;
j += t->size * 2;
}
break;
case JSMN_PRIMITIVE:
case JSMN_STRING:
if (stack[level].t->type == JSMN_OBJECT &&
stack[level].n_items % 2 == 1) {
/* key */
if (json_token_eq(response, t, "token")) {
stack[level].label = TOKEN;
fprintf(stderr, "token\n");
}
if (json_token_eq(response, t,
"catalog")) {
stack[level].label = CATALOG;
fprintf(stderr, "catalog\n");
}
if (json_token_eq(response, t, "endpoints")) {
stack[level].label = ENDPOINTS;
}
if (json_token_eq(response, t, "region")) {
json_token_str(response, &tokens[i + 1],
region, sizeof(region));
}
if (json_token_eq(response, t, "url")) {
json_token_str(response, &tokens[i + 1],
url, sizeof(url));
}
if (json_token_eq(response, t, "interface")) {
json_token_str(response, &tokens[i + 1],
interface, sizeof(interface));
}
if (json_token_eq(response, t, "type")) {
json_token_str(response, &tokens[i + 1],
type, sizeof(type));
}
}
break;
}
while (stack[level].n_items == 0 && level > 0) {
if (stack[level].t->type == JSMN_OBJECT
&& level == 6
&& stack[level - 1].t->type == JSMN_ARRAY
&& stack[level - 2].label == ENDPOINTS) {
if ((opt_swift_region == NULL
|| strcmp(opt_swift_region, region) == 0)
&& strcmp(interface, "public") == 0) {
strncpy(filtered_url, url,
sizeof(filtered_url));
}
}
if (stack[level].t->type == JSMN_OBJECT &&
level == 4 &&
stack[level - 1].t->type == JSMN_ARRAY &&
stack[level - 2].label == CATALOG) {
if (strcmp(type, "object-store") == 0) {
strncpy(auth_info->url, filtered_url,
sizeof(auth_info->url));
}
}
--level;
}
}
free(tokens);
assert(level == 0);
if (*auth_info->url == 0) {
fprintf(stderr, "error: can not get URL from response\n");
return(false);
}
return(true);
}
/*********************************************************************//**
Captures X-Subject-Token header. */
static
size_t keystone_v3_header_cb(char *ptr, size_t size, size_t nmemb, void *data)
{
swift_auth_info *info = (swift_auth_info*)(data);
get_http_header("X-Subject-Token: ", ptr,
info->token, array_elements(info->token));
return nmemb * size;
}
/*********************************************************************//**
Authenticate against Swift TempAuth. Fills swift_auth_info struct.
Uses creadentials privided as global variables.
@returns true if access is granted and token received. */
static
bool
swift_keystone_auth_v3(const char *auth_url, swift_auth_info *info)
{
char scope[SWIFT_MAX_URL_SIZE];
char domain[SWIFT_MAX_URL_SIZE];
char payload[SWIFT_MAX_URL_SIZE];
struct curl_slist *slist = NULL;
download_buffer_info buf_info;
long http_code;
CURLcode res;
CURL *curl;
bool auth_res = false;
memset(&buf_info, 0, sizeof(buf_info));
buf_info.custom_header_callback = keystone_v3_header_cb;
buf_info.custom_header_callback_data = info;
if (opt_swift_user == NULL) {
fprintf(stderr, "error: both --swift-user is required "
"for keystone authentication.\n");
return(false);
}
if (opt_swift_password == NULL) {
fprintf(stderr, "error: both --swift-password is required "
"for keystone authentication.\n");
return(false);
}
if (opt_swift_project_id != NULL && opt_swift_project != NULL) {
fprintf(stderr, "error: both --swift-project and "
"--swift-project-id specified for keystone "
"authentication.\n");
return(false);
}
if (opt_swift_domain_id != NULL && opt_swift_domain != NULL) {
fprintf(stderr, "error: both --swift-domain and "
"--swift-domain-id specified for keystone "
"authentication.\n");
return(false);
}
if (opt_swift_project_id != NULL && opt_swift_domain != NULL) {
fprintf(stderr, "error: both --swift-project-id and "
"--swift-domain specified for keystone "
"authentication.\n");
return(false);
}
if (opt_swift_project_id != NULL && opt_swift_domain_id != NULL) {
fprintf(stderr, "error: both --swift-project-id and "
"--swift-domain-id specified for keystone "
"authentication.\n");
return(false);
}
scope[0] = 0; domain[0] = 0;
if (opt_swift_domain != NULL) {
snprintf(domain, sizeof(domain),
",{\"domain\":{\"name\":\"%s\"}}",
opt_swift_domain);
} else if (opt_swift_domain_id != NULL) {
snprintf(domain, sizeof(domain),
",{\"domain\":{\"id\":\"%s\"}}",
opt_swift_domain_id);
}
if (opt_swift_project_id != NULL) {
snprintf(scope, sizeof(scope),
",\"scope\":{\"project\":{\"id\":\"%s\"}}",
opt_swift_project_id);
} else if (opt_swift_project != NULL) {
snprintf(scope, sizeof(scope),
",\"scope\":{\"project\":{\"name\":\"%s\"%s}}",
opt_swift_project, domain);
}
snprintf(payload, sizeof(payload), "{\"auth\":{\"identity\":"
"{\"methods\":[\"password\"],\"password\":{\"user\":"
"{\"name\":\"%s\",\"password\":\"%s\"%s}}}%s}}",
opt_swift_user, opt_swift_password,
*scope ? "" : ",\"domain\":{\"id\":\"default\"}",
scope);
curl = curl_easy_init();
if (curl != NULL) {
slist = curl_slist_append(slist,
"Content-Type: application/json");
slist = curl_slist_append(slist,
"Accept: application/json");
curl_easy_setopt(curl, CURLOPT_VERBOSE, opt_verbose);
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_URL, auth_url);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fetch_buffer_cb);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf_info);
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION,
fetch_buffer_header_cb);
curl_easy_setopt(curl, CURLOPT_HEADERDATA,
&buf_info);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
if (opt_cacert != NULL)
curl_easy_setopt(curl, CURLOPT_CAINFO, opt_cacert);
if (opt_insecure)
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE);
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr,
"error: curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
goto cleanup;
}
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
if (http_code < 200 || http_code >= 300) {
fprintf(stderr, "error: request failed "
"with response code: %ld\n", http_code);
res = CURLE_LOGIN_DENIED;
goto cleanup;
}
} else {
res = CURLE_FAILED_INIT;
fprintf(stderr, "error: curl_easy_init() failed\n");
goto cleanup;
}
if (!swift_parse_keystone_response_v3(buf_info.buf,
buf_info.size, info)) {
goto cleanup;
}
auth_res = true;
cleanup:
if (slist) {
curl_slist_free_all(slist);
}
if (curl) {
curl_easy_cleanup(curl);
}
free(buf_info.buf);
return(auth_res);
}
int main(int argc, char **argv)
{
swift_auth_info info;
char auth_url[SWIFT_MAX_URL_SIZE];
MY_INIT(argv[0]);
/* handle_options in parse_args is destructive so
* we make a copy of our argument pointers so we can
* mask the sensitive values afterwards */
char **mask_argv = (char **)malloc(sizeof(char *) * (argc - 1));
memcpy(mask_argv, argv + 1, sizeof(char *) * (argc - 1));
if (parse_args(argc, argv)) {
return(EXIT_FAILURE);
}
mask_args(argc, mask_argv); /* mask args on cmdline */
curl_global_init(CURL_GLOBAL_ALL);
if (opt_swift_auth_version == NULL || *opt_swift_auth_version == '1') {
/* TempAuth */
snprintf(auth_url, SWIFT_MAX_URL_SIZE, "%sauth/v%s/",
opt_swift_auth_url, opt_swift_auth_version ?
opt_swift_auth_version : "1.0");
if (!swift_temp_auth(auth_url, &info)) {
fprintf(stderr, "error: failed to authenticate\n");
return(EXIT_FAILURE);
}
} else if (*opt_swift_auth_version == '2') {
/* Keystone v2 */
snprintf(auth_url, SWIFT_MAX_URL_SIZE, "%sv%s/tokens",
opt_swift_auth_url, opt_swift_auth_version);
if (!swift_keystone_auth_v2(auth_url, &info)) {
fprintf(stderr, "error: failed to authenticate\n");
return(EXIT_FAILURE);
}
} else if (*opt_swift_auth_version == '3') {
/* Keystone v3 */
snprintf(auth_url, SWIFT_MAX_URL_SIZE, "%sv%s/auth/tokens",
opt_swift_auth_url, opt_swift_auth_version);
if (!swift_keystone_auth_v3(auth_url, &info)) {
fprintf(stderr, "error: failed to authenticate\n");
exit(EXIT_FAILURE);
}
}
if (opt_swift_storage_url != NULL) {
snprintf(info.url, sizeof(info.url), "%s",
opt_swift_storage_url);
}
fprintf(stderr, "Object store URL: %s\n", info.url);
if (opt_mode == MODE_PUT) {
if (swift_create_container(&info, opt_swift_container) != 0) {
fprintf(stderr, "error: failed to create "
"container %s\n",
opt_swift_container);
return(EXIT_FAILURE);
}
if (swift_backup_exists(&info, opt_swift_container, opt_name)) {
fprintf(stderr, "error: backup named '%s' "
"already exists!\n",
opt_name);
return(EXIT_FAILURE);
}
if (swift_upload_parts(&info, opt_swift_container,
opt_name) != 0) {
fprintf(stderr, "error: upload failed\n");
return(EXIT_FAILURE);
}
} else if (opt_mode == MODE_GET) {
if (swift_download(&info, opt_swift_container, opt_name)
!= CURLE_OK) {
fprintf(stderr, "error: download failed\n");
return(EXIT_FAILURE);
}
} else if (opt_mode == MODE_DELETE) {
if (swift_delete(&info, opt_swift_container, opt_name)
!= CURLE_OK) {
fprintf(stderr, "error: delete failed\n");
return(EXIT_FAILURE);
}
} else {
fprintf(stderr, "Unknown command supplied.\n");
exit(EXIT_FAILURE);
}
curl_global_cleanup();
return(EXIT_SUCCESS);
}