From 44df2b9d1d8f60f2219cf178314e1d14376a4db2 Mon Sep 17 00:00:00 2001 From: Mitya_Eremeev Date: Fri, 22 Oct 2021 22:26:07 +0300 Subject: [PATCH 1/9] Initialize variables to avoid segfault. If we call 'keystone_end' right after 'keystone_start' segfault occurs. It's clear that it's not expected user behavior, but possible segfaults should be avoided. --- keystone-client.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/keystone-client.c b/keystone-client.c index 0694750..5498edf 100644 --- a/keystone-client.c +++ b/keystone-client.c @@ -169,6 +169,9 @@ keystone_start(keystone_context_t *context) return KSERR_INIT_FAILED; } + context->pvt.auth_token = NULL; + context->pvt.auth_payload = NULL; + context->pvt.json_tokeniser = NULL; return KSERR_SUCCESS; } From 8826c30fbb9d8301c91a52154a32d5a8b1ca8592 Mon Sep 17 00:00:00 2001 From: Mitya_Eremeev Date: Fri, 29 Oct 2021 21:46:41 +0300 Subject: [PATCH 2/9] Fix token getting. Code was obsolete - last commit was 4 yesrs ago. Request and response formats were changed since then. Also added small test to check obtaining of token. Also added brief howto in readme. --- README.md | 11 +++++ keystone-client.c | 105 +++++++++++++++++++++++----------------------- tests.c | 48 +++++++++++++++++++++ 3 files changed, 112 insertions(+), 52 deletions(-) create mode 100644 tests.c diff --git a/README.md b/README.md index bcdc8ad..02a971f 100644 --- a/README.md +++ b/README.md @@ -4,3 +4,14 @@ keystone-client C client for OpenStack Keystone authentication service Depends on libcurl and libjson-c. + +Build and launch on Ubuntu +========================== +$ sudo apt install libcurl4-openssl-dev libjson-c-dev + +$ gcc -g3 -c -pedantic -Wall -fpic keystone-client.c -lcurl -ljson-c && \ +gcc -shared -o libkeystone-client.so keystone-client.o + +$ export LD_LIBRARY_PATH=:$LD_LIBRARY_PATH + +$ ./test diff --git a/keystone-client.c b/keystone-client.c index 5498edf..391117b 100644 --- a/keystone-client.c +++ b/keystone-client.c @@ -1,6 +1,5 @@ #include #include -#include #include "keystone-client.h" @@ -12,22 +11,13 @@ /* The content-type we desire to receive in authentication responses */ #define KEYSTONE_AUTH_RESPONSE_FORMAT MIME_TYPE_JSON /* The portion of a JSON-encoded Keystone credentials POST body preceding the username */ -#define KEYSTONE_AUTH_PAYLOAD_BEFORE_USERNAME "\ -{\n\ - \"auth\":{\n\ - \"passwordCredentials\":{\n\ - \"username\":\"" +#define KEYSTONE_AUTH_PAYLOAD_BEFORE_USERNAME "{\"auth\": {\"identity\": {\"methods\": [\"password\"],\"password\": {\"user\": {\"domain\": {\"name\": \"Default\"},\"name\": \"" /* The portion of a JSON-encoded Keystone credentials POST body succeeding the username and preceding the password */ -#define KEYSTONE_AUTH_PAYLOAD_BEFORE_PASSWORD "\",\n\ - \"password\":\"" +#define KEYSTONE_AUTH_PAYLOAD_BEFORE_PASSWORD "\", \"password\": \"" /* The portion of a JSON-encoded Keystone credentials POST body succeeding the password and preceding the tenant name */ -#define KEYSTONE_AUTH_PAYLOAD_BEFORE_TENANT "\"\n\ - },\n\ - \"tenantName\":\"" +#define KEYSTONE_AUTH_PAYLOAD_BEFORE_TENANT "\"}}}, \"scope\": {\"project\":{\"domain\":{\"name\": \"Default\"}, \"name\": \"" /* The portion of a JSON-encoded Keystone credentials POST body succeeding the tenant name */ -#define KEYSTONE_AUTH_PAYLOAD_END "\"\n\ - }\n\ -}" +#define KEYSTONE_AUTH_PAYLOAD_END "\"}}}}" /* Number of elements in a statically-sized array */ #define ELEMENTSOF(arr) (sizeof(arr) / sizeof((arr)[0])) @@ -55,7 +45,7 @@ static const char *const openstack_service_endpoint_url_type_names[] = { "internalURL" }; -/* HUman-friendly names for service endpoint URL types */ +/* Human-friendly names for service endpoint URL types */ static const char *const openstack_service_endpoint_url_type_friendly_names[] = { "public", "admin", @@ -344,7 +334,6 @@ json_array_find(keystone_context_t *context, struct json_object *array, item_cal return item; default: assert(0); - break; } } @@ -562,7 +551,7 @@ keystone_get_service_url(keystone_context_t *context, enum openstack_service des static enum keystone_error process_keystone_json(keystone_context_t *context, struct json_object *response) { - struct json_object *access, *token, *id; + struct json_object *token; if (context->pvt.debug) { json_object_to_file_ext("/dev/stderr", response, JSON_C_TO_STRING_PRETTY); @@ -571,50 +560,24 @@ process_keystone_json(keystone_context_t *context, struct json_object *response) context->keystone_error("response is not an object", KSERR_PARSE); return KSERR_PARSE; /* Not the expected JSON object */ } - /* Everything is in an "access" sub-object */ - if (!json_object_object_get_ex(response, "access", &access)) { - context->keystone_error("response lacks 'access' key", KSERR_PARSE); + /* Catalog in "token" -> "catalog" */ + if (!json_object_object_get_ex(response, "token", &token)) { + context->keystone_error("response lacks 'token' key", KSERR_PARSE); return KSERR_PARSE; /* Lacking the expected key */ } - if (!json_object_is_type(access, json_type_object)) { - context->keystone_error("response.access is not an object", KSERR_PARSE); + if (!json_object_is_type(token, json_type_object)) { + context->keystone_error("response.token is not an object", KSERR_PARSE); return KSERR_PARSE; /* Not the expected JSON object */ } - /* Service catalog */ - if (!json_object_object_get_ex(access, "serviceCatalog", &context->pvt.services)) { - context->keystone_error("response.access lacks 'serviceCatalog' key", KSERR_PARSE); + /* Catalog */ + if (!json_object_object_get_ex(token, "catalog", &context->pvt.services)) { + context->keystone_error("response.token lacks 'catalog' key", KSERR_PARSE); return KSERR_PARSE; } if (!json_object_is_type(context->pvt.services, json_type_array)) { - context->keystone_error("response.access.serviceCatalog not an array", KSERR_PARSE); + context->keystone_error("response.token.catalog not an array", KSERR_PARSE); return KSERR_PARSE; } - /* Authentication token */ - if (!json_object_object_get_ex(access, "token", &token)) { - context->keystone_error("reponse.access lacks 'token' key", KSERR_PARSE); - return KSERR_PARSE; /* Lacking the expected key */ - } - if (!json_object_is_type(token, json_type_object)) { - context->keystone_error("response.access.token is not an object", KSERR_PARSE); - return KSERR_PARSE; /* Not the expected JSON object */ - } - if (!json_object_object_get_ex(token, "id", &id)) { - context->keystone_error("response.access.token lacks 'id' key", KSERR_PARSE); - return KSERR_PARSE; /* Lacking the expected key */ - } - if (!json_object_is_type(id, json_type_string)) { - context->keystone_error("response.access.token.id is not a string", KSERR_PARSE); - return KSERR_PARSE; /* Not the expected JSON string */ - } - context->pvt.auth_token = context->allocator( - context->pvt.auth_token, - json_object_get_string_len(id) - + 1 /* '\0' */ - ); - if (NULL == context->pvt.auth_token) { - return KSERR_PARSE; /* Allocation failed */ - } - strcpy(context->pvt.auth_token, json_object_get_string(id)); return KSERR_SUCCESS; } @@ -652,6 +615,27 @@ process_keystone_response(void *ptr, size_t size, size_t nmemb, void *userdata) return len; /* Inform libcurl that all data were 'handled' */ } +static size_t +process_keystone_response_headers(char *buffer, size_t size, size_t nitems, + void *userdata) { +/* Authentication token */ + size_t len = size * nitems; + keystone_context_t *context = (keystone_context_t *) userdata; + if (strstr(strtok(buffer, ":"), "X-Subject-Token")) { + char *token = strtok(NULL, "\n"); + context->pvt.auth_token = context->allocator(context->pvt.auth_token, + strlen(token) + ); + if (NULL == context->pvt.auth_token) { + return 0; /* Allocation failed */ + } + strcpy(context->pvt.auth_token, token); + printf("token in process_keystone_response_headers: %s\n", + context->pvt.auth_token); + } + return len; +} + /** * Authenticate against a Keystone authentication service with the given tenant and user names and password. * This yields an authorisation token, which is then used to access all Swift services. @@ -772,6 +756,23 @@ keystone_authenticate(keystone_context_t *context, const char *url, const char * return KSERR_URL_FAILED; } + curl_err = curl_easy_setopt(context->pvt.curl, CURLOPT_HEADERDATA, + context); + if (CURLE_OK != curl_err) { + context->curl_error("curl_easy_setopt", curl_err); + curl_slist_free_all(headers); + return KSERR_URL_FAILED; + } + + curl_err = curl_easy_setopt(context->pvt.curl, + CURLOPT_HEADERFUNCTION, + process_keystone_response_headers); + if (CURLE_OK != curl_err) { + context->curl_error("curl_easy_setopt", curl_err); + curl_slist_free_all(headers); + return KSERR_URL_FAILED; + } + curl_err = curl_easy_perform(context->pvt.curl); if (CURLE_OK != curl_err) { context->curl_error("curl_easy_perform", curl_err); diff --git a/tests.c b/tests.c new file mode 100644 index 0000000..289d73d --- /dev/null +++ b/tests.c @@ -0,0 +1,48 @@ +#include "keystone-client.h" + +int main(void) +{ + keystone_context_t context; + enum keystone_error result; + + result = keystone_global_init(); + printf("keystone_global_init result: %d\n", result); + + /* we must initialise with false values in order keystone_start could + * assign default values for context */ + /* TODO: eliminate preliminary assignment - do default initialisation + * inside keystone_start */ + context.curl_error = NULL; + context.json_error = NULL; + context.keystone_error = NULL; + context.allocator = NULL; + result = keystone_start(&context); + printf("keystone_start result: %d\n", result); + + result = keystone_set_debug(&context, 1); + printf("keystone_set_debug result: %d\n", result); + + const char url[] = "http://192.168.122.216/identity/v3/auth/tokens"; + const char tenant_name[] = "admin"; + const char username[] = "admin"; + const char password[] = "secret"; + result = keystone_authenticate(&context, url, tenant_name, username, + password); + printf("keystone_authenticate result: %d\n", result); + + const char *token = keystone_get_auth_token(&context); + if (token != NULL) { + printf("token: %s\n", token); + } + + keystone_end(&context); + printf("keystone_end called\n"); + + keystone_global_cleanup(); + printf("keystone_global_cleanup called\n"); + + return 0; +} + + + From 5a44c540f84c9d2ec40724073d28a6f3410a3e54 Mon Sep 17 00:00:00 2001 From: Mitya_Eremeev Date: Wed, 3 Nov 2021 19:19:37 +0300 Subject: [PATCH 3/9] Added how to build tests --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 02a971f..d8e8edd 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,9 @@ $ sudo apt install libcurl4-openssl-dev libjson-c-dev $ gcc -g3 -c -pedantic -Wall -fpic keystone-client.c -lcurl -ljson-c && \ gcc -shared -o libkeystone-client.so keystone-client.o +$ gcc -g3 -L -Wall -o test tests.c \ +-lkeystone-client -lcurl -ljson-c + $ export LD_LIBRARY_PATH=:$LD_LIBRARY_PATH $ ./test From 526fb0c37605b80c6393c02c6e70a1185fb182cd Mon Sep 17 00:00:00 2001 From: Mitya_Eremeev Date: Wed, 3 Nov 2021 22:13:22 +0300 Subject: [PATCH 4/9] Get keystone credentials from env --- README.md | 5 +++++ tests.c | 11 ++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d8e8edd..9b652d0 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,11 @@ gcc -shared -o libkeystone-client.so keystone-client.o $ gcc -g3 -L -Wall -o test tests.c \ -lkeystone-client -lcurl -ljson-c +$ export KSTEST_ADMIN_URL=http:///identity +$ export OS_PROJECT_NAME=admin +$ export KSTEST_ADMIN_USERNAME=admin +$ export KSTEST_ADMIN_PASSWORD=secret + $ export LD_LIBRARY_PATH=:$LD_LIBRARY_PATH $ ./test diff --git a/tests.c b/tests.c index 289d73d..da45a6e 100644 --- a/tests.c +++ b/tests.c @@ -1,3 +1,5 @@ +#include + #include "keystone-client.h" int main(void) @@ -21,11 +23,10 @@ int main(void) result = keystone_set_debug(&context, 1); printf("keystone_set_debug result: %d\n", result); - - const char url[] = "http://192.168.122.216/identity/v3/auth/tokens"; - const char tenant_name[] = "admin"; - const char username[] = "admin"; - const char password[] = "secret"; + const char *url = strcat(getenv("KSTEST_ADMIN_URL"), "/v3/auth/tokens"); + const char *tenant_name = getenv("OS_PROJECT_NAME"); + const char *username = getenv("KSTEST_ADMIN_USERNAME"); + const char *password = getenv("KSTEST_ADMIN_PASSWORD"); result = keystone_authenticate(&context, url, tenant_name, username, password); printf("keystone_authenticate result: %d\n", result); From deae743c407cecf8f9b0c1a5c722795a2c680ab7 Mon Sep 17 00:00:00 2001 From: Mitya_Eremeev Date: Fri, 12 Nov 2021 15:51:50 +0300 Subject: [PATCH 5/9] Provided default credentials. KSTEST_ADMIN_URL = http://192.168.122.216/identity/v3/auth/tokens OS_PROJECT_NAME = admin KSTEST_ADMIN_USERNAME = admin KSTEST_ADMIN_PASSWORD = secret --- tests.c | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/tests.c b/tests.c index da45a6e..c563eae 100644 --- a/tests.c +++ b/tests.c @@ -23,10 +23,40 @@ int main(void) result = keystone_set_debug(&context, 1); printf("keystone_set_debug result: %d\n", result); - const char *url = strcat(getenv("KSTEST_ADMIN_URL"), "/v3/auth/tokens"); - const char *tenant_name = getenv("OS_PROJECT_NAME"); - const char *username = getenv("KSTEST_ADMIN_USERNAME"); - const char *password = getenv("KSTEST_ADMIN_PASSWORD"); + + char *temp_var = NULL; + const char *url; + temp_var = getenv("KSTEST_ADMIN_URL"); + if (temp_var) { + url = strcat(temp_var, "/v3/auth/tokens"); + } + else { + url = "http://192.168.122.216/identity/v3/auth/tokens"; + } + const char *tenant_name; + temp_var = getenv("OS_PROJECT_NAME"); + if (temp_var) { + tenant_name = temp_var; + } + else { + tenant_name = "admin"; + } + const char *username; + temp_var = getenv("KSTEST_ADMIN_USERNAME"); + if (temp_var) { + username = temp_var; + } + else { + username = "admin"; + } + const char *password; + temp_var = getenv("KSTEST_ADMIN_PASSWORD"); + if (temp_var) { + password = temp_var; + } + else { + password = "secret"; + } result = keystone_authenticate(&context, url, tenant_name, username, password); printf("keystone_authenticate result: %d\n", result); From ee2206b49eb95b3c6f06032e4a33ca828cd578f8 Mon Sep 17 00:00:00 2001 From: Mitya_Eremeev Date: Fri, 12 Nov 2021 17:56:30 +0300 Subject: [PATCH 6/9] Formed test into functions for readability. --- tests.c | 59 +++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/tests.c b/tests.c index c563eae..5538b95 100644 --- a/tests.c +++ b/tests.c @@ -2,28 +2,37 @@ #include "keystone-client.h" -int main(void) -{ - keystone_context_t context; - enum keystone_error result; - - result = keystone_global_init(); +enum keystone_error +init() { + enum keystone_error result = keystone_global_init(); printf("keystone_global_init result: %d\n", result); + return result; +} +enum keystone_error +start(keystone_context_t *context){ /* we must initialise with false values in order keystone_start could * assign default values for context */ /* TODO: eliminate preliminary assignment - do default initialisation * inside keystone_start */ - context.curl_error = NULL; - context.json_error = NULL; - context.keystone_error = NULL; - context.allocator = NULL; - result = keystone_start(&context); + context->curl_error = NULL; + context->json_error = NULL; + context->keystone_error = NULL; + context->allocator = NULL; + enum keystone_error result = keystone_start(context); printf("keystone_start result: %d\n", result); + return result; +} - result = keystone_set_debug(&context, 1); +enum keystone_error +set_debug(keystone_context_t *context){ + enum keystone_error result = keystone_set_debug(context, 1); printf("keystone_set_debug result: %d\n", result); + return result; +} +enum keystone_error +authenticate(keystone_context_t *context){ char *temp_var = NULL; const char *url; temp_var = getenv("KSTEST_ADMIN_URL"); @@ -57,21 +66,39 @@ int main(void) else { password = "secret"; } - result = keystone_authenticate(&context, url, tenant_name, username, - password); + enum keystone_error result = keystone_authenticate(context, url, + tenant_name, username, password); printf("keystone_authenticate result: %d\n", result); + return result; +} - const char *token = keystone_get_auth_token(&context); +void print_token (keystone_context_t *context){ + const char *token = keystone_get_auth_token(context); if (token != NULL) { printf("token: %s\n", token); } +} - keystone_end(&context); +void end (keystone_context_t *context){ + keystone_end(context); printf("keystone_end called\n"); +} +void cleanup (){ keystone_global_cleanup(); printf("keystone_global_cleanup called\n"); +} +int main(void) +{ + keystone_context_t context; + init(); + start(&context); + set_debug(&context); + authenticate(&context); + print_token(&context); + end(&context); + cleanup(); return 0; } From 4608411070bc582e54517c51fe5303c39937f1ff Mon Sep 17 00:00:00 2001 From: Mitya_Eremeev Date: Fri, 12 Nov 2021 19:31:57 +0300 Subject: [PATCH 7/9] Stricter compilation rules. --- README.md | 9 +++++---- keystone-client.c | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9b652d0..5a59531 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,12 @@ Build and launch on Ubuntu ========================== $ sudo apt install libcurl4-openssl-dev libjson-c-dev -$ gcc -g3 -c -pedantic -Wall -fpic keystone-client.c -lcurl -ljson-c && \ -gcc -shared -o libkeystone-client.so keystone-client.o +$ gcc -g3 -c -pedantic -Wall -Wextra -Wformat-security -std=c18 -fpic \ +keystone-client.c -lcurl -ljson-c \ +&& gcc -shared -o libkeystone-client.so keystone-client.o -$ gcc -g3 -L -Wall -o test tests.c \ --lkeystone-client -lcurl -ljson-c +$ gcc -g3 -L -Wall -Wextra -Wformat-security -std=c18 -o test \ +tests.c -lkeystone-client -lcurl -ljson-c $ export KSTEST_ADMIN_URL=http:///identity $ export OS_PROJECT_NAME=admin diff --git a/keystone-client.c b/keystone-client.c index 391117b..9ae9411 100644 --- a/keystone-client.c +++ b/keystone-client.c @@ -251,7 +251,7 @@ json_array_length(keystone_context_t *context, struct json_object *array) if (len < 0) { context->keystone_error("JSON array length is negative", KSERR_PARSE); len = 0; - } else if (len > UINT_MAX) { + } else if ((unsigned int)len > UINT_MAX) { context->keystone_error("JSON array length is too large for unsigned int", KSERR_PARSE); len = 0; } From f2b50f71931158c6f9ac908ecc8c3bc9cda27878 Mon Sep 17 00:00:00 2001 From: Mitya_Eremeev Date: Fri, 12 Nov 2021 20:01:36 +0300 Subject: [PATCH 8/9] Lines no longer 80 chars. --- keystone-client.c | 306 +++++++++++++++++++++++++++++++--------------- keystone-client.h | 129 +++++++++++-------- 2 files changed, 286 insertions(+), 149 deletions(-) diff --git a/keystone-client.c b/keystone-client.c index 9ae9411..fdb920c 100644 --- a/keystone-client.c +++ b/keystone-client.c @@ -10,13 +10,20 @@ #define KEYSTONE_AUTH_REQUEST_FORMAT MIME_TYPE_JSON /* The content-type we desire to receive in authentication responses */ #define KEYSTONE_AUTH_RESPONSE_FORMAT MIME_TYPE_JSON -/* The portion of a JSON-encoded Keystone credentials POST body preceding the username */ -#define KEYSTONE_AUTH_PAYLOAD_BEFORE_USERNAME "{\"auth\": {\"identity\": {\"methods\": [\"password\"],\"password\": {\"user\": {\"domain\": {\"name\": \"Default\"},\"name\": \"" -/* The portion of a JSON-encoded Keystone credentials POST body succeeding the username and preceding the password */ +/* The portion of a JSON-encoded Keystone credentials POST body preceding the + * username */ +#define KEYSTONE_AUTH_PAYLOAD_BEFORE_USERNAME "{\"auth\": {\"identity\": \ +{\"methods\": [\"password\"],\"password\": {\"user\": {\"domain\": {\"name\": \ +\"Default\"},\"name\": \"" +/* The portion of a JSON-encoded Keystone credentials POST body succeeding the + * username and preceding the password */ #define KEYSTONE_AUTH_PAYLOAD_BEFORE_PASSWORD "\", \"password\": \"" -/* The portion of a JSON-encoded Keystone credentials POST body succeeding the password and preceding the tenant name */ -#define KEYSTONE_AUTH_PAYLOAD_BEFORE_TENANT "\"}}}, \"scope\": {\"project\":{\"domain\":{\"name\": \"Default\"}, \"name\": \"" -/* The portion of a JSON-encoded Keystone credentials POST body succeeding the tenant name */ +/* The portion of a JSON-encoded Keystone credentials POST body succeeding the + * password and preceding the tenant name */ +#define KEYSTONE_AUTH_PAYLOAD_BEFORE_TENANT "\"}}}, \"scope\": \ +{\"project\":{\"domain\":{\"name\": \"Default\"}, \"name\": \"" +/* The portion of a JSON-encoded Keystone credentials POST body succeeding the + * tenant name */ #define KEYSTONE_AUTH_PAYLOAD_END "\"}}}}" /* Number of elements in a statically-sized array */ #define ELEMENTSOF(arr) (sizeof(arr) / sizeof((arr)[0])) @@ -46,7 +53,8 @@ static const char *const openstack_service_endpoint_url_type_names[] = { }; /* Human-friendly names for service endpoint URL types */ -static const char *const openstack_service_endpoint_url_type_friendly_names[] = { +static const char *const openstack_service_endpoint_url_type_friendly_names[] = + { "public", "admin", "internal" @@ -59,30 +67,35 @@ static void default_curl_error_callback(const char *curl_funcname, CURLcode curl_err) { assert(curl_funcname != NULL); - fprintf(stderr, "%s failed: libcurl error code %ld: %s\n", curl_funcname, (long) curl_err, curl_easy_strerror(curl_err)); + fprintf(stderr, "%s failed: libcurl error code %ld: %s\n", curl_funcname, + (long) curl_err, curl_easy_strerror(curl_err)); } /** * Default handler for libjson errors. */ static void -default_json_error_callback(const char *json_funcname, enum json_tokener_error json_err) +default_json_error_callback(const char *json_funcname, + enum json_tokener_error json_err) { assert(json_funcname != NULL); assert(json_err != json_tokener_success); assert(json_err != json_tokener_continue); - fprintf(stderr, "%s failed: libjson error %ld: %s\n", json_funcname, (long) json_err, json_tokener_error_desc(json_err)); + fprintf(stderr, "%s failed: libjson error %ld: %s\n", json_funcname, + (long) json_err, json_tokener_error_desc(json_err)); } /** * Default handler for Keystone errors. */ static void -default_keystone_error_callback(const char *keystone_operation, enum keystone_error keystone_err) +default_keystone_error_callback(const char *keystone_operation, + enum keystone_error keystone_err) { assert(keystone_operation != NULL); assert(keystone_err != KSERR_SUCCESS); - fprintf(stderr, "Keystone: %s: error %ld\n", keystone_operation, (long) keystone_err); + fprintf(stderr, "Keystone: %s: error %ld\n", keystone_operation, + (long) keystone_err); } /** @@ -154,7 +167,8 @@ keystone_start(keystone_context_t *context) } context->pvt.curl = curl_easy_init(); if (NULL == context->pvt.curl) { - /* NOTE: No error code from libcurl, so we assume/invent CURLE_FAILED_INIT */ + /* NOTE: No error code from libcurl, + * so we assume/invent CURLE_FAILED_INIT */ context->curl_error("curl_easy_init", CURLE_FAILED_INIT); return KSERR_INIT_FAILED; } @@ -180,10 +194,12 @@ keystone_end(keystone_context_t *context) curl_easy_cleanup(context->pvt.curl); context->pvt.curl = NULL; if (context->pvt.auth_token != NULL) { - context->pvt.auth_token = context->allocator(context->pvt.auth_token, 0); + context->pvt.auth_token = context->allocator(context->pvt.auth_token, + 0); } if (context->pvt.auth_payload != NULL) { - context->pvt.auth_payload = context->allocator(context->pvt.auth_payload, 0); + context->pvt.auth_payload = context->allocator( + context->pvt.auth_payload, 0); } if (context->pvt.json_tokeniser != NULL) { json_tokener_free(context->pvt.json_tokeniser); @@ -192,8 +208,8 @@ keystone_end(keystone_context_t *context) } /** - * Control whether a proxy (eg HTTP or SOCKS) is used to access the Keystone server. - * Argument must be a URL, or NULL if no proxy is to be used. + * Control whether a proxy (eg HTTP or SOCKS) is used to access the Keystone + * server. Argument must be a URL, or NULL if no proxy is to be used. */ enum keystone_error keystone_set_proxy(keystone_context_t *context, const char *proxy_url) @@ -203,7 +219,8 @@ keystone_set_proxy(keystone_context_t *context, const char *proxy_url) assert(context != NULL); assert(context->pvt.curl != NULL); - curl_err = curl_easy_setopt(context->pvt.curl, CURLOPT_PROXY, (NULL == proxy_url) ? "" : proxy_url); + curl_err = curl_easy_setopt(context->pvt.curl, CURLOPT_PROXY, + (NULL == proxy_url) ? "" : proxy_url); if (CURLE_OK != curl_err) { context->curl_error("curl_easy_setopt", curl_err); return KSERR_INVARG; @@ -213,8 +230,9 @@ keystone_set_proxy(keystone_context_t *context, const char *proxy_url) } /** - * Control verbose logging to stderr of the actions of this library and the libraries it uses. - * Currently this enables logging to standard error of libcurl's actions. + * Control verbose logging to stderr of the actions of this library and the + * libraries it uses. Currently this enables logging to standard error of + * libcurl's actions. */ enum keystone_error keystone_set_debug(keystone_context_t *context, unsigned int enable_debugging) @@ -226,7 +244,8 @@ keystone_set_debug(keystone_context_t *context, unsigned int enable_debugging) context->pvt.debug = enable_debugging; - curl_err = curl_easy_setopt(context->pvt.curl, CURLOPT_VERBOSE, enable_debugging ? 1 : 0); + curl_err = curl_easy_setopt(context->pvt.curl, CURLOPT_VERBOSE, + enable_debugging ? 1 : 0); if (CURLE_OK != curl_err) { context->curl_error("curl_easy_setopt", curl_err); return KSERR_INVARG; @@ -249,10 +268,13 @@ json_array_length(keystone_context_t *context, struct json_object *array) if (json_object_is_type(array, json_type_array)) { len = json_object_array_length(array); if (len < 0) { - context->keystone_error("JSON array length is negative", KSERR_PARSE); + context->keystone_error("JSON array length is negative", + KSERR_PARSE); len = 0; } else if ((unsigned int)len > UINT_MAX) { - context->keystone_error("JSON array length is too large for unsigned int", KSERR_PARSE); + context->keystone_error( + "JSON array length is too large for unsigned int", + KSERR_PARSE); len = 0; } } else { @@ -267,7 +289,8 @@ json_array_length(keystone_context_t *context, struct json_object *array) * Return the index'th element of the given JSON array. */ static struct json_object * -json_array_get(keystone_context_t *context, struct json_object *array, unsigned int index) +json_array_get(keystone_context_t *context, struct json_object *array, + unsigned int index) { struct json_object *item; @@ -278,10 +301,12 @@ json_array_get(keystone_context_t *context, struct json_object *array, unsigned if (index < json_array_length(context, array)) { item = json_object_array_get_idx(array, index); if (NULL == item) { - context->keystone_error("failed to index into JSON array", KSERR_PARSE); + context->keystone_error("failed to index into JSON array", + KSERR_PARSE); } } else { - context->keystone_error("JSON array index out of bound", KSERR_PARSE); + context->keystone_error("JSON array index out of bound", + KSERR_PARSE); item = NULL; } } else { @@ -304,16 +329,21 @@ typedef enum item_callback_return item_callback_return_t; /** * A function which receives items from a JSON array. */ -typedef item_callback_return_t (*item_callback_func_t)(keystone_context_t *context, struct json_object *item, void *callback_arg); +typedef item_callback_return_t (*item_callback_func_t)( + keystone_context_t *context, struct json_object *item, + void *callback_arg); /** - * Iterate over the given JSON array, passing elements of it to the given iteration function. - * If the iteration function ever returns STOP, return the array element passed to it which caused it to first return STOP. - * If the iteration function returns CONTINUE for each and every array element, return NULL. - * The iteration function is not guaranteed to be called for each and every element in the given array. + * Iterate over the given JSON array, passing elements of it to the given + * iteration function. If the iteration function ever returns STOP, return the + * array element passed to it which caused it to first return STOP. If the + * iteration function returns CONTINUE for each and every array element, return + * NULL. The iteration function is not guaranteed to be called for each and + * every element in the given array. */ static struct json_object * -json_array_find(keystone_context_t *context, struct json_object *array, item_callback_func_t callback, void *callback_arg) +json_array_find(keystone_context_t *context, struct json_object *array, + item_callback_func_t callback, void *callback_arg) { struct json_object *item; unsigned int i; @@ -350,16 +380,19 @@ service_name(unsigned int service) const char * endpoint_url_name(unsigned int endpoint) { - assert(endpoint < ELEMENTSOF(openstack_service_endpoint_url_type_friendly_names)); + assert(endpoint < ELEMENTSOF( + openstack_service_endpoint_url_type_friendly_names)); return openstack_service_endpoint_url_type_friendly_names[endpoint]; } /** - * If the OpenStack service represented by the given JSON object is of the type name given by callback_arg, return STOP. - * Otherwise, if it is of some other type or if its type cannot be determined, return CONTINUE. + * If the OpenStack service represented by the given JSON object is of the type + * name given by callback_arg, return STOP. Otherwise, if it is of some other + * type or if its type cannot be determined, return CONTINUE. */ static item_callback_return_t -filter_service_by_type(keystone_context_t *context, struct json_object *service, void *callback_arg) +filter_service_by_type(keystone_context_t *context, struct json_object *service, + void *callback_arg) { const char *desired_type = (const char *) callback_arg; struct json_object *service_type; @@ -369,15 +402,21 @@ filter_service_by_type(keystone_context_t *context, struct json_object *service, assert(callback_arg != NULL); if (!json_object_is_type(service, json_type_object)) { - context->keystone_error("response.access.serviceCatalog[n] is not an object", KSERR_PARSE); + context->keystone_error( + "response.access.serviceCatalog[n] is not an object", + KSERR_PARSE); return CONTINUE; } if (!json_object_object_get_ex(service, "type", &service_type)) { - context->keystone_error("response.access.serviceCatalog[n] lacks a 'type' key", KSERR_PARSE); + context->keystone_error( + "response.access.serviceCatalog[n] lacks a 'type' key", + KSERR_PARSE); return CONTINUE; } if (!json_object_is_type(service_type, json_type_string)) { - context->keystone_error("response.access.serviceCatalog[n].type is not a string", KSERR_PARSE); + context->keystone_error( + "response.access.serviceCatalog[n].type is not a string", + KSERR_PARSE); return CONTINUE; } if (0 != strcmp(json_object_get_string(service_type), desired_type)) { @@ -388,43 +427,54 @@ filter_service_by_type(keystone_context_t *context, struct json_object *service, } /** - * Given a JSON array representing a list of OpenStack services, find the first service of the given-named type. - * If a service of the given type name is found, return it. - * Otherwise, if no service of the given type name is found, return NULL. + * Given a JSON array representing a list of OpenStack services, find the first + * service of the given-named type. If a service of the given type name is + * found, return it. Otherwise, if no service of the given type name is found, + * return NULL. */ struct json_object * -find_service_by_type_name(keystone_context_t *context, struct json_object *services, const char *desired_type) +find_service_by_type_name(keystone_context_t *context, + struct json_object *services, + const char *desired_type) { assert(context != NULL); assert(services != NULL); assert(desired_type != NULL); - return json_array_find(context, services, filter_service_by_type, (void *) desired_type); + return json_array_find(context, services, filter_service_by_type, + (void *) desired_type); } /** - * Given a JSON array representing a list of OpenStack services, find the first service of the given type. - * Otherwise, if a service of the given type is found, return it. - * Otherwise, if no service of the given type is found, return NULL. + * Given a JSON array representing a list of OpenStack services, find the first + * service of the given type. Otherwise, if a service of the given type is + * found, return it. Otherwise, if no service of the given type is found, return + * NULL. */ struct json_object * -find_service_by_type(keystone_context_t *context, struct json_object *services, enum openstack_service desired_type) +find_service_by_type(keystone_context_t *context, struct json_object *services, + enum openstack_service desired_type) { assert(context != NULL); assert(services != NULL); assert((unsigned int) desired_type < ELEMENTSOF(openstack_service_names)); assert(openstack_service_names[(unsigned int) desired_type] != NULL); - return json_array_find(context, services, filter_service_by_type, (void *) openstack_service_names[(unsigned int) desired_type]); + return json_array_find( + context, services, filter_service_by_type, + (void *) openstack_service_names[(unsigned int) desired_type]); } /** - * Given a JSON object representing an endpoint of an OpenStack service and a desired endpoint version, - * if the given endpoint appears to have the given API version, or it has no versionID attribute, return STOP. - * Otherwise, if the endpoint's version is not the desired version, or the endpoint's version is erroneous, return CONTINUE. + * Given a JSON object representing an endpoint of an OpenStack service and a + * desired endpoint version, if the given endpoint appears to have the given API + * version, or it has no versionID attribute, return STOP. Otherwise, if the + * endpoint's version is not the desired version, or the endpoint's version is + * erroneous, return CONTINUE. */ static item_callback_return_t -filter_endpoint_by_version(keystone_context_t *context, json_object *endpoint, void *callback_arg) +filter_endpoint_by_version(keystone_context_t *context, json_object *endpoint, + void *callback_arg) { unsigned int desired_api_version; struct json_object *endpoint_api_version; @@ -435,16 +485,25 @@ filter_endpoint_by_version(keystone_context_t *context, json_object *endpoint, v desired_api_version = *((unsigned int *) callback_arg); if (!json_object_is_type(endpoint, json_type_object)) { - context->keystone_error("response.access.serviceCatalog[n].endpoints[n] is not an object", KSERR_PARSE); + context->keystone_error( + "response.access.serviceCatalog[n].endpoints[n] is not an " + "object", + KSERR_PARSE); return CONTINUE; } - if (!json_object_object_get_ex(endpoint, "versionId", &endpoint_api_version)) { - /* Keystone documentation includes a versionID key, but it is not present in the responses I've seen */ - /* context->keystone_error("response.access.serviceCatalog[n].endpoints[n] lacks a 'versionId' key", KSERR_PARSE); */ + if (!json_object_object_get_ex(endpoint, "versionId", + &endpoint_api_version)) { + /* Keystone documentation includes a versionID key, but it is not + * present in the responses I've seen context->keystone_error( + * "response.access.serviceCatalog[n].endpoints[n] lacks a 'versionId' + * key", KSERR_PARSE); */ return STOP; /* Take a lack of versionID to mean a catch-all */ } if (!json_object_is_type(endpoint_api_version, json_type_string)) { - context->keystone_error("response.access.serviceCatalog[n].endpoints[n].versionId is not a string", KSERR_PARSE); + context->keystone_error( + "response.access.serviceCatalog[n].endpoints[n].versionId is " + "not a string", + KSERR_PARSE); return CONTINUE; /* Version attribute wrong type */ } if (json_object_get_double(endpoint_api_version) != desired_api_version) { @@ -456,25 +515,33 @@ filter_endpoint_by_version(keystone_context_t *context, json_object *endpoint, v } /** - * Given a JSON object representing an OpenStack service, find the first endpoint of the given version. - * If an endpoint of the given version is found, return it. - * Otherwise, if no endpoint of the given version is found, return NULL. + * Given a JSON object representing an OpenStack service, find the first + * endpoint of the given version. If an endpoint of the given version is found, + * return it. Otherwise, if no endpoint of the given version is found, return + * NULL. */ static struct json_object * -service_find_endpoint_by_version(keystone_context_t *context, struct json_object *service, unsigned int desired_api_version) +service_find_endpoint_by_version(keystone_context_t *context, + struct json_object *service, + unsigned int desired_api_version) { struct json_object *endpoints, *endpoint; if (json_object_object_get_ex(service, "endpoints", &endpoints)) { if (0 == desired_api_version) { - /* No desired API version currently set, so use the first endpoint found */ + /* No desired API version currently set, so use the first endpoint + * found */ endpoint = json_array_get(context, endpoints, 0); } else { /* Looking for a certain version of the Swift RESTful API */ - endpoint = json_array_find(context, endpoints, filter_endpoint_by_version, (void *) &desired_api_version); + endpoint = json_array_find(context, endpoints, + filter_endpoint_by_version, + (void *) &desired_api_version); } } else { - context->keystone_error("response.access.serviceCatalog[n] lacks an 'endpoints' key", KSERR_PARSE); + context->keystone_error("response.access.serviceCatalog[n] lacks an " + "'endpoints' key", + KSERR_PARSE); endpoint = NULL; /* Lacking the expected key */ } @@ -482,11 +549,13 @@ service_find_endpoint_by_version(keystone_context_t *context, struct json_object } /** - * Given a JSON object representing an OpenStack service endpoint, return its URL of the given type, if any. + * Given a JSON object representing an OpenStack service endpoint, return its + * URL of the given type, if any. * If the service endpoint has no URL */ static const char * -endpoint_url(keystone_context_t *context, struct json_object *endpoint, enum openstack_service_endpoint_url_type endpoint_url_type) +endpoint_url(keystone_context_t *context, struct json_object *endpoint, + enum openstack_service_endpoint_url_type endpoint_url_type) { struct json_object *endpoint_public_url; const char *url_val; @@ -494,20 +563,30 @@ endpoint_url(keystone_context_t *context, struct json_object *endpoint, enum ope assert(context != NULL); assert(endpoint != NULL); - assert(endpoint_url_type < ELEMENTSOF(openstack_service_endpoint_url_type_names)); - assert(openstack_service_endpoint_url_type_names[(unsigned int) endpoint_url_type] != NULL); + assert(endpoint_url_type < ELEMENTSOF( + openstack_service_endpoint_url_type_names)); + assert(openstack_service_endpoint_url_type_names + [(unsigned int)endpoint_url_type] != NULL); - url_type_name = openstack_service_endpoint_url_type_names[(unsigned int) endpoint_url_type]; + url_type_name = openstack_service_endpoint_url_type_names + [(unsigned int) endpoint_url_type]; - if (json_object_object_get_ex(endpoint, url_type_name, &endpoint_public_url)) { + if (json_object_object_get_ex(endpoint, url_type_name, + &endpoint_public_url)) { if (json_object_is_type(endpoint_public_url, json_type_string)) { url_val = json_object_get_string(endpoint_public_url); } else { - context->keystone_error("response.access.serviceCatalog[n].endpoints[n] URL is not a string", KSERR_PARSE); + context->keystone_error( + "response.access.serviceCatalog[n].endpoints[n] URL is not " + "a string", + KSERR_PARSE); url_val = NULL; } } else { - context->keystone_error("response.access.serviceCatalog[n].endpoints[n] lacks a URL key of the requested type", KSERR_PARSE); + context->keystone_error( + "response.access.serviceCatalog[n].endpoints[n] lacks a URL " + "key of the requested type", + KSERR_PARSE); url_val = NULL; } @@ -515,23 +594,31 @@ endpoint_url(keystone_context_t *context, struct json_object *endpoint, enum ope } /** - * Given a desired service type and version and type of URL, find a service of the given type in Keystone's catalog of services, - * then find an endpoint of that service with the given API version, then return its URL of the given type. - * Return NULL if the service cannot be found, or if no endpoint of the given version can be found, - * or if the service endpoint of the given version has no URL of the given type. + * Given a desired service type and version and type of URL, find a service of + * the given type in Keystone's catalog of services, then find an endpoint of + * that service with the given API version, then return its URL of the given + * type. Return NULL if the service cannot be found, or if no endpoint of the + * given version can be found, or if the service endpoint of the given version + * has no URL of the given type. */ const char * -keystone_get_service_url(keystone_context_t *context, enum openstack_service desired_service_type, unsigned int desired_api_version, enum openstack_service_endpoint_url_type endpoint_url_type) +keystone_get_service_url( + keystone_context_t *context, + enum openstack_service desired_service_type, + unsigned int desired_api_version, + enum openstack_service_endpoint_url_type endpoint_url_type) { struct json_object *service; const char *url; assert(context != NULL); - service = find_service_by_type(context, context->pvt.services, desired_service_type); + service = find_service_by_type(context, context->pvt.services, + desired_service_type); if (service) { static struct json_object *endpoint; - endpoint = service_find_endpoint_by_version(context, service, desired_api_version); + endpoint = service_find_endpoint_by_version(context, service, + desired_api_version); if (endpoint) { url = endpoint_url(context, endpoint, endpoint_url_type); } else { @@ -545,8 +632,9 @@ keystone_get_service_url(keystone_context_t *context, enum openstack_service des } /** - * Retrieve the authentication token and service catalog from a now-complete Keystone JSON response, - * and store them in the Keystone context structure for later use. + * Retrieve the authentication token and service catalog from a now-complete + * Keystone JSON response, and store them in the Keystone context structure for + * later use. */ static enum keystone_error process_keystone_json(keystone_context_t *context, struct json_object *response) @@ -554,7 +642,8 @@ process_keystone_json(keystone_context_t *context, struct json_object *response) struct json_object *token; if (context->pvt.debug) { - json_object_to_file_ext("/dev/stderr", response, JSON_C_TO_STRING_PRETTY); + json_object_to_file_ext("/dev/stderr", response, + JSON_C_TO_STRING_PRETTY); } if (!json_object_is_type(response, json_type_object)) { context->keystone_error("response is not an object", KSERR_PARSE); @@ -571,11 +660,13 @@ process_keystone_json(keystone_context_t *context, struct json_object *response) } /* Catalog */ if (!json_object_object_get_ex(token, "catalog", &context->pvt.services)) { - context->keystone_error("response.token lacks 'catalog' key", KSERR_PARSE); + context->keystone_error("response.token lacks 'catalog' key", + KSERR_PARSE); return KSERR_PARSE; } if (!json_object_is_type(context->pvt.services, json_type_array)) { - context->keystone_error("response.token.catalog not an array", KSERR_PARSE); + context->keystone_error("response.token.catalog not an array", + KSERR_PARSE); return KSERR_PARSE; } @@ -584,7 +675,8 @@ process_keystone_json(keystone_context_t *context, struct json_object *response) /** * Process a Keystone authentication response. - * This parses the response and saves copies of the interesting service endpoint URLs. + * This parses the response and saves copies of the interesting service endpoint + * URLs. */ static size_t process_keystone_response(void *ptr, size_t size, size_t nmemb, void *userdata) @@ -602,14 +694,16 @@ process_keystone_response(void *ptr, size_t size, size_t nmemb, void *userdata) if (json_tokener_success == json_err) { enum keystone_error sc_err = process_keystone_json(context, jobj); if (sc_err != KSERR_SUCCESS) { - return 0; /* Failed to process JSON. Inform libcurl no data 'handled' */ + return 0; /* Failed to process JSON. Inform libcurl no data + * 'handled' */ } } else if (json_tokener_continue == json_err) { /* Complete JSON response not yet received; continue */ } else { context->json_error("json_tokener_parse_ex", json_err); context->keystone_error("failed to parse response", KSERR_PARSE); - return 0; /* Apparent JSON parsing problem. Inform libcurl no data 'handled' */ + return 0; /* Apparent JSON parsing problem. Inform libcurl no data + * 'handled' */ } return len; /* Inform libcurl that all data were 'handled' */ @@ -637,11 +731,14 @@ process_keystone_response_headers(char *buffer, size_t size, size_t nitems, } /** - * Authenticate against a Keystone authentication service with the given tenant and user names and password. - * This yields an authorisation token, which is then used to access all Swift services. + * Authenticate against a Keystone authentication service with the given tenant + * and user names and password. This yields an authorisation token, which is + * then used to access all Swift services. */ enum keystone_error -keystone_authenticate(keystone_context_t *context, const char *url, const char *tenant_name, const char *username, const char *password) +keystone_authenticate(keystone_context_t *context, const char *url, + const char *tenant_name, const char *username, + const char *password) { CURLcode curl_err; struct curl_slist *headers = NULL; @@ -668,7 +765,8 @@ keystone_authenticate(keystone_context_t *context, const char *url, const char * if (NULL == context->pvt.json_tokeniser) { context->pvt.json_tokeniser = json_tokener_new(); if (NULL == context->pvt.json_tokeniser) { - context->keystone_error("json_tokener_new failed", KSERR_INIT_FAILED); + context->keystone_error("json_tokener_new failed", + KSERR_INIT_FAILED); return KSERR_INIT_FAILED; } } else { @@ -687,10 +785,13 @@ keystone_authenticate(keystone_context_t *context, const char *url, const char * return KSERR_URL_FAILED; } - /* Append header specifying body content type (since this differs from libcurl's default) */ - headers = curl_slist_append(headers, "Content-Type: " KEYSTONE_AUTH_REQUEST_FORMAT); + /* Append header specifying body content type (since this differs from + * libcurl's default) */ + headers = curl_slist_append(headers, + "Content-Type: " KEYSTONE_AUTH_REQUEST_FORMAT); - /* Append pseudo-header defeating libcurl's default addition of an "Expect: 100-continue" header. */ + /* Append pseudo-header defeating libcurl's default addition of an "Expect: + * 100-continue" header. */ headers = curl_slist_append(headers, "Expect:"); /* Generate POST request body containing the authentication credentials */ @@ -717,15 +818,18 @@ keystone_authenticate(keystone_context_t *context, const char *url, const char * fputs(context->pvt.auth_payload, stderr); } - /* Pass the POST request body to libcurl. The data are not copied, so they must persist during the request lifetime. */ - curl_err = curl_easy_setopt(context->pvt.curl, CURLOPT_POSTFIELDS, context->pvt.auth_payload); + /* Pass the POST request body to libcurl. The data are not copied, so they + * must persist during the request lifetime. */ + curl_err = curl_easy_setopt(context->pvt.curl, CURLOPT_POSTFIELDS, + context->pvt.auth_payload); if (CURLE_OK != curl_err) { context->curl_error("curl_easy_setopt", curl_err); curl_slist_free_all(headers); return KSERR_URL_FAILED; } - curl_err = curl_easy_setopt(context->pvt.curl, CURLOPT_POSTFIELDSIZE, body_len); + curl_err = curl_easy_setopt(context->pvt.curl, CURLOPT_POSTFIELDSIZE, + body_len); if (CURLE_OK != curl_err) { context->curl_error("curl_easy_setopt", curl_err); curl_slist_free_all(headers); @@ -733,7 +837,8 @@ keystone_authenticate(keystone_context_t *context, const char *url, const char * } /* Add header requesting desired response content type */ - headers = curl_slist_append(headers, "Accept: " KEYSTONE_AUTH_RESPONSE_FORMAT); + headers = curl_slist_append(headers, + "Accept: " KEYSTONE_AUTH_RESPONSE_FORMAT); curl_err = curl_easy_setopt(context->pvt.curl, CURLOPT_HTTPHEADER, headers); if (CURLE_OK != curl_err) { @@ -742,7 +847,8 @@ keystone_authenticate(keystone_context_t *context, const char *url, const char * return KSERR_URL_FAILED; } - curl_err = curl_easy_setopt(context->pvt.curl, CURLOPT_WRITEFUNCTION, process_keystone_response); + curl_err = curl_easy_setopt(context->pvt.curl, CURLOPT_WRITEFUNCTION, + process_keystone_response); if (CURLE_OK != curl_err) { context->curl_error("curl_easy_setopt", curl_err); curl_slist_free_all(headers); diff --git a/keystone-client.h b/keystone-client.h index 3d947c6..e1a7a30 100644 --- a/keystone-client.h +++ b/keystone-client.h @@ -8,8 +8,8 @@ /** * High-level types of errors which can occur while attempting to use Keystone. - * More detail is available from lower-level libraries (such as curl and libjson) - * using error callbacks specific to those libraries. + * More detail is available from lower-level libraries (such as curl and + * libjson) using error callbacks specific to those libraries. */ enum keystone_error { KSERR_SUCCESS = 0, /* Success */ @@ -18,7 +18,8 @@ enum keystone_error { KSERR_ALLOC_FAILED = 3, /* Memory allocation failed */ KSERR_URL_FAILED = 4, /* Network operation on a URL failed */ KSERR_AUTH_REJECTED = 5, /* Authentication attempt rejected */ - KSERR_NOTFOUND = 6, /* Requested service(s) not found in service catalog */ + KSERR_NOTFOUND = 6, /* Requested service(s) not found in service + * catalog */ KSERR_PARSE = 7 /* Failed to parse Keystone response */ }; @@ -52,10 +53,14 @@ struct keystone_context_private { unsigned int debug; struct json_tokener *json_tokeniser; /* libjson0 library's JSON tokeniser */ struct json_object *services; /* service catalog JSON array */ - unsigned int verify_cert_trusted; /* True if the peer's certificate must chain to a trusted CA, false otherwise */ - unsigned int verify_cert_hostname; /* True if the peer's certificate's hostname must be correct, false otherwise */ - char *auth_payload; /* Authentication POST payload, containing credentials */ - char *auth_token; /* Authentication token previously obtained from Keystone */ + unsigned int verify_cert_trusted; /* True if the peer's certificate must + * chain to a trusted CA, false otherwise */ + unsigned int verify_cert_hostname; /* True if the peer's certificate's + * hostname must be correct, false otherwise */ + char *auth_payload; /* Authentication POST payload, containing + * credentials */ + char *auth_token; /* Authentication token previously obtained from + * Keystone */ }; typedef struct keystone_context_private keystone_context_private_t; @@ -67,51 +72,59 @@ typedef void *(*keystone_allocator_func_t)(void *ptr, size_t newsize); typedef void (*curl_error_callback_t)(const char *curl_funcname, CURLcode res); /* A function which receives libjson errors */ -typedef void (*json_error_callback_t)(const char *json_funcname, enum json_tokener_error json_err); +typedef void (*json_error_callback_t)(const char *json_funcname, + enum json_tokener_error json_err); /* A function which receives Keystone errors */ -typedef void (*keystone_error_callback_t)(const char *keystone_operation, enum keystone_error keystone_err); +typedef void (*keystone_error_callback_t)(const char *keystone_operation, + enum keystone_error keystone_err); /** * All use of this library is performed within a 'context'. - * Contexts cannot be shared among threads; each thread must have its own context. - * Your program is responsible for allocating and freeing context structures. - * Contexts should be zeroed out prior to use. + * Contexts cannot be shared among threads; each thread must have its own + * context. Your program is responsible for allocating and freeing context + * structures. Contexts should be zeroed out prior to use. */ struct keystone_context { - /* These members are 'public'; your program can (and should) set them at will */ + /* These members are 'public'; your program can (and should) set them at + * will */ /** * Called when a libcurl error occurs. - * Your program may set this function pointer in order to perform custom error handling. - * If this is NULL at the time keystone_start is called, a default handler will be used. + * Your program may set this function pointer in order to perform custom + * error handling. If this is NULL at the time keystone_start is called, + * a default handler will be used. */ curl_error_callback_t curl_error; /** * Called when a libjson error occurs. - * Your program may set this function in order to perform custom error handling. - * If this is NULL at the time keystone_start is called, a default handler will be used. + * Your program may set this function in order to perform custom error + * handling. If this is NULL at the time keystone_start is called, a default + * handler will be used. */ json_error_callback_t json_error; /** * Called when a Keystone error occurs. - * Your program may set this function in order to perform custom error handling. - * If this is NULL at the time keystone_start is called, a default handler will be used. + * Your program may set this function in order to perform custom error + * handling. If this is NULL at the time keystone_start is called, a default + * handler will be used. */ keystone_error_callback_t keystone_error; /** * Called when this library needs to allocate, re-allocate or free memory. * If size is zero and ptr is NULL, nothing is done. - * If size is zero and ptr is non-NULL, the previously-allocated memory at ptr is to be freed. - * If size is non-zero and ptr is NULL, memory of the given size is to be allocated. - * If size is non-zero and ptr is non-NULL, the previously-allocated memory at ptr - * is to be re-allocated to be the given size. - * If this function pointer is NULL at the time keystone_start is called, a default re-allocator will be used. + * If size is zero and ptr is non-NULL, the previously-allocated memory at + * ptr is to be freed. If size is non-zero and ptr is NULL, memory of the + * given size is to be allocated. If size is non-zero and ptr is non-NULL, + * the previously-allocated memory at ptr is to be re-allocated to be the + * given size. If this function pointer is NULL at the time keystone_start + * is called, a default re-allocator will be used. */ keystone_allocator_func_t allocator; /* This member (and its members, recursively) are 'private'. */ - /* They should not be modified by your program unless you *really* know what you're doing. */ + /* They should not be modified by your program unless you *really* know what + * you're doing. */ keystone_context_private_t pvt; }; @@ -125,9 +138,9 @@ typedef struct keystone_context keystone_context_t; * This must be called early in the execution of your program, * before additional threads (if any) are created. * This must be called before any other use of this library by your program. - * These restrictions are imposed by libcurl, and the libcurl restrictions are in turn - * imposed by the libraries that libcurl uses. - * If your program is a library, it will need to expose a similar API to, + * These restrictions are imposed by libcurl, and the libcurl restrictions are + * in turn imposed by the libraries that libcurl uses. If your program is a + * library, it will need to expose a similar API to, * and expose similar restrictions on, its users. */ enum keystone_error keystone_global_init(void); @@ -136,12 +149,13 @@ enum keystone_error keystone_global_init(void); * Cease using this library. * This must be called late in the execution of your program, * after all secondary threads (if any) have exited, - * so that there is precisely one thread in your program at the time of the call. + * so that there is precisely one thread in your program at the time of the + * call. * This library must not be used by your program after this function is called. - * This function must be called exactly once for each successful prior call to keystone_global_init - * by your program. - * These restrictions are imposed by libcurl, and the libcurl restrictions are in turn - * imposed by the libraries that libcurl uses. + * This function must be called exactly once for each successful prior call + * to keystone_global_init by your program. + * These restrictions are imposed by libcurl, and the libcurl restrictions are + * in turn imposed by the libraries that libcurl uses. * If your program is a library, it will need to expose a similar API to, * and expose similar restrictions on, its users. */ @@ -149,39 +163,49 @@ void keystone_global_cleanup(void); /** * Begin using this library for a single thread of your program. - * This must be called by each thread of your program in order to use this library. + * This must be called by each thread of your program in order to use this + * library. */ enum keystone_error keystone_start(keystone_context_t *context); /** * Cease using this library for a single thread. - * This must be called by each thread of your program after it is finished using this library. - * Each thread in your program must call this function precisely once for each successful prior call - * to keystone_start by that thread. + * This must be called by each thread of your program after it is finished using + * this library. + * Each thread in your program must call this function precisely once for each + * successful prior call to keystone_start by that thread. * After this call, the context is invalid. */ void keystone_end(keystone_context_t *context); /** - * Control whether a proxy (eg HTTP or SOCKS) is used to access the Keystone server. + * Control whether a proxy (eg HTTP or SOCKS) is used to access the Keystone + * server. * Argument must be a URL, or NULL if no proxy is to be used. */ -enum keystone_error keystone_set_proxy(keystone_context_t *context, const char *proxy_url); +enum keystone_error keystone_set_proxy(keystone_context_t *context, + const char *proxy_url); /** - * Control verbose logging to stderr of the actions of this library and the libraries it uses. + * Control verbose logging to stderr of the actions of this library and the + * libraries it uses. * Currently this enables logging to standard error of libcurl's actions. */ -enum keystone_error keystone_set_debug(keystone_context_t *context, unsigned int enable_debugging); +enum keystone_error keystone_set_debug(keystone_context_t *context, + unsigned int enable_debugging); const char *service_name(unsigned int service); const char *endpoint_url_name(unsigned int endpoint); /** - * Authenticate against a Keystone authentication service with the given tenant and user names and password. - * This yields an authorisation token, which is then used to access all Swift services. + * Authenticate against a Keystone authentication service with the given tenant + * and user names and password. + * This yields an authorisation token, which is then used to access all Swift + * services. */ -enum keystone_error keystone_authenticate(keystone_context_t *context, const char *url, const char *tenant_name, const char *username, const char *password); +enum keystone_error keystone_authenticate(keystone_context_t *context, + const char *url, const char *tenant_name, const char *username, + const char *password); /** * Return the previously-acquired Keystone authentication token, if any. @@ -190,11 +214,18 @@ enum keystone_error keystone_authenticate(keystone_context_t *context, const cha const char *keystone_get_auth_token(keystone_context_t *context); /** - * Given a desired service type and version and type of URL, find a service of the given type in Keystone's catalog of services, - * then find an endpoint of that service with the given API version, then return its URL of the given type. - * Return NULL if the service cannot be found, or if no endpoint of the given version can be found, - * or if the service endpoint of the given version has no URL of the given type. + * Given a desired service type and version and type of URL, find a service of + * the given type in Keystone's catalog of services, then find an endpoint of + * that service with the given API version, then return its URL of the given + * type. + * Return NULL if the service cannot be found, or if no endpoint of the given + * version can be found, or if the service endpoint of the given version has no + * URL of the given type. */ -const char *keystone_get_service_url(keystone_context_t *context, enum openstack_service desired_service_type, unsigned int desired_api_version, enum openstack_service_endpoint_url_type endpoint_url_type); +const char *keystone_get_service_url( + keystone_context_t *context, + enum openstack_service desired_service_type, + unsigned int desired_api_version, + enum openstack_service_endpoint_url_type endpoint_url_type); #endif /* KEYSTONE_CLIENT_H_ */ From ec74551554d3cdc191f45d9f9e3e26a8853129b2 Mon Sep 17 00:00:00 2001 From: Mitya_Eremeev Date: Fri, 10 Dec 2021 19:19:26 +0300 Subject: [PATCH 9/9] Remove all except getting token. All methods are obsolete and have no use. So removed them. The only operation method is getting token. --- README.md | 19 +- keystone-client.c | 499 +--------------------------------- keystone-client.h | 23 +- tests.c => tests/smoke_test.c | 4 +- 4 files changed, 18 insertions(+), 527 deletions(-) rename tests.c => tests/smoke_test.c (97%) diff --git a/README.md b/README.md index 5a59531..c933abb 100644 --- a/README.md +++ b/README.md @@ -3,18 +3,21 @@ keystone-client C client for OpenStack Keystone authentication service -Depends on libcurl and libjson-c. +Depends on libcurl. -Build and launch on Ubuntu -========================== -$ sudo apt install libcurl4-openssl-dev libjson-c-dev +Build library +============= +$ sudo apt install libcurl4-openssl-dev $ gcc -g3 -c -pedantic -Wall -Wextra -Wformat-security -std=c18 -fpic \ -keystone-client.c -lcurl -ljson-c \ +keystone-client.c -lcurl \ && gcc -shared -o libkeystone-client.so keystone-client.o -$ gcc -g3 -L -Wall -Wextra -Wformat-security -std=c18 -o test \ -tests.c -lkeystone-client -lcurl -ljson-c +Build and run smoke test +======================== +$ cd tests +$ gcc -g3 -L -Wall -Wextra -Wformat-security -std=c18 -o \ +smoke_test smoke_test.c -lkeystone-client -lcurl $ export KSTEST_ADMIN_URL=http:///identity $ export OS_PROJECT_NAME=admin @@ -23,4 +26,4 @@ $ export KSTEST_ADMIN_PASSWORD=secret $ export LD_LIBRARY_PATH=:$LD_LIBRARY_PATH -$ ./test +$ ./smoke_test diff --git a/keystone-client.c b/keystone-client.c index fdb920c..424bd8f 100644 --- a/keystone-client.c +++ b/keystone-client.c @@ -42,16 +42,6 @@ static const char *const openstack_service_names[] = { "image" /* Glance */ }; -/** - * Service endpoint URL type names, as seen in JSON object keys. - * Order must match that in enum openstack_service_endpoint_type. - */ -static const char *const openstack_service_endpoint_url_type_names[] = { - "publicURL", - "adminURL", - "internalURL" -}; - /* Human-friendly names for service endpoint URL types */ static const char *const openstack_service_endpoint_url_type_friendly_names[] = { @@ -71,20 +61,6 @@ default_curl_error_callback(const char *curl_funcname, CURLcode curl_err) (long) curl_err, curl_easy_strerror(curl_err)); } -/** - * Default handler for libjson errors. - */ -static void -default_json_error_callback(const char *json_funcname, - enum json_tokener_error json_err) -{ - assert(json_funcname != NULL); - assert(json_err != json_tokener_success); - assert(json_err != json_tokener_continue); - fprintf(stderr, "%s failed: libjson error %ld: %s\n", json_funcname, - (long) json_err, json_tokener_error_desc(json_err)); -} - /** * Default handler for Keystone errors. */ @@ -156,9 +132,6 @@ keystone_start(keystone_context_t *context) if (!context->curl_error) { context->curl_error = default_curl_error_callback; } - if (!context->json_error) { - context->json_error = default_json_error_callback; - } if (!context->keystone_error) { context->keystone_error = default_keystone_error_callback; } @@ -175,7 +148,6 @@ keystone_start(keystone_context_t *context) context->pvt.auth_token = NULL; context->pvt.auth_payload = NULL; - context->pvt.json_tokeniser = NULL; return KSERR_SUCCESS; } @@ -201,10 +173,6 @@ keystone_end(keystone_context_t *context) context->pvt.auth_payload = context->allocator( context->pvt.auth_payload, 0); } - if (context->pvt.json_tokeniser != NULL) { - json_tokener_free(context->pvt.json_tokeniser); - context->pvt.json_tokeniser = NULL; - } } /** @@ -254,122 +222,6 @@ keystone_set_debug(keystone_context_t *context, unsigned int enable_debugging) return KSERR_SUCCESS; } -/** - * Return the length of the given JSON array. - */ -static unsigned int -json_array_length(keystone_context_t *context, struct json_object *array) -{ - int len; - - assert(context != NULL); - assert(array != NULL); - - if (json_object_is_type(array, json_type_array)) { - len = json_object_array_length(array); - if (len < 0) { - context->keystone_error("JSON array length is negative", - KSERR_PARSE); - len = 0; - } else if ((unsigned int)len > UINT_MAX) { - context->keystone_error( - "JSON array length is too large for unsigned int", - KSERR_PARSE); - len = 0; - } - } else { - context->keystone_error("JSON object is not an array", KSERR_PARSE); - len = 0; - } - - return (unsigned int) len; -} - -/** - * Return the index'th element of the given JSON array. - */ -static struct json_object * -json_array_get(keystone_context_t *context, struct json_object *array, - unsigned int index) -{ - struct json_object *item; - - assert(context != NULL); - assert(array != NULL); - - if (json_object_is_type(array, json_type_array)) { - if (index < json_array_length(context, array)) { - item = json_object_array_get_idx(array, index); - if (NULL == item) { - context->keystone_error("failed to index into JSON array", - KSERR_PARSE); - } - } else { - context->keystone_error("JSON array index out of bound", - KSERR_PARSE); - item = NULL; - } - } else { - context->keystone_error("JSON object is not an array", KSERR_PARSE); - item = NULL; - } - return item; -} - -/** - * Types of return from an enumerator function for a JSON array. - */ -enum item_callback_return { - CONTINUE = 0, /* Continue iterating */ - STOP = 1 /* Cease iteration */ -}; - -typedef enum item_callback_return item_callback_return_t; - -/** - * A function which receives items from a JSON array. - */ -typedef item_callback_return_t (*item_callback_func_t)( - keystone_context_t *context, struct json_object *item, - void *callback_arg); - -/** - * Iterate over the given JSON array, passing elements of it to the given - * iteration function. If the iteration function ever returns STOP, return the - * array element passed to it which caused it to first return STOP. If the - * iteration function returns CONTINUE for each and every array element, return - * NULL. The iteration function is not guaranteed to be called for each and - * every element in the given array. - */ -static struct json_object * -json_array_find(keystone_context_t *context, struct json_object *array, - item_callback_func_t callback, void *callback_arg) -{ - struct json_object *item; - unsigned int i; - - assert(context != NULL); - assert(array != NULL); - assert(callback != NULL); - - i = json_array_length(context, array); - while (i--) { - item_callback_return_t ret; - item = json_array_get(context, array, i); - ret = callback(context, item, callback_arg); - switch (ret) { - case CONTINUE: - break; - case STOP: - return item; - default: - assert(0); - } - } - - return NULL; -} - const char * service_name(unsigned int service) { @@ -385,330 +237,6 @@ endpoint_url_name(unsigned int endpoint) return openstack_service_endpoint_url_type_friendly_names[endpoint]; } -/** - * If the OpenStack service represented by the given JSON object is of the type - * name given by callback_arg, return STOP. Otherwise, if it is of some other - * type or if its type cannot be determined, return CONTINUE. - */ -static item_callback_return_t -filter_service_by_type(keystone_context_t *context, struct json_object *service, - void *callback_arg) -{ - const char *desired_type = (const char *) callback_arg; - struct json_object *service_type; - - assert(context != NULL); - assert(service != NULL); - assert(callback_arg != NULL); - - if (!json_object_is_type(service, json_type_object)) { - context->keystone_error( - "response.access.serviceCatalog[n] is not an object", - KSERR_PARSE); - return CONTINUE; - } - if (!json_object_object_get_ex(service, "type", &service_type)) { - context->keystone_error( - "response.access.serviceCatalog[n] lacks a 'type' key", - KSERR_PARSE); - return CONTINUE; - } - if (!json_object_is_type(service_type, json_type_string)) { - context->keystone_error( - "response.access.serviceCatalog[n].type is not a string", - KSERR_PARSE); - return CONTINUE; - } - if (0 != strcmp(json_object_get_string(service_type), desired_type)) { - return CONTINUE; /* Not the service type we're after */ - } - - return STOP; /* Acceptable */ -} - -/** - * Given a JSON array representing a list of OpenStack services, find the first - * service of the given-named type. If a service of the given type name is - * found, return it. Otherwise, if no service of the given type name is found, - * return NULL. - */ -struct json_object * -find_service_by_type_name(keystone_context_t *context, - struct json_object *services, - const char *desired_type) -{ - assert(context != NULL); - assert(services != NULL); - assert(desired_type != NULL); - - return json_array_find(context, services, filter_service_by_type, - (void *) desired_type); -} - -/** - * Given a JSON array representing a list of OpenStack services, find the first - * service of the given type. Otherwise, if a service of the given type is - * found, return it. Otherwise, if no service of the given type is found, return - * NULL. - */ -struct json_object * -find_service_by_type(keystone_context_t *context, struct json_object *services, - enum openstack_service desired_type) -{ - assert(context != NULL); - assert(services != NULL); - assert((unsigned int) desired_type < ELEMENTSOF(openstack_service_names)); - assert(openstack_service_names[(unsigned int) desired_type] != NULL); - - return json_array_find( - context, services, filter_service_by_type, - (void *) openstack_service_names[(unsigned int) desired_type]); -} - -/** - * Given a JSON object representing an endpoint of an OpenStack service and a - * desired endpoint version, if the given endpoint appears to have the given API - * version, or it has no versionID attribute, return STOP. Otherwise, if the - * endpoint's version is not the desired version, or the endpoint's version is - * erroneous, return CONTINUE. - */ -static item_callback_return_t -filter_endpoint_by_version(keystone_context_t *context, json_object *endpoint, - void *callback_arg) -{ - unsigned int desired_api_version; - struct json_object *endpoint_api_version; - - assert(context != NULL); - assert(endpoint != NULL); - assert(callback_arg != NULL); - - desired_api_version = *((unsigned int *) callback_arg); - if (!json_object_is_type(endpoint, json_type_object)) { - context->keystone_error( - "response.access.serviceCatalog[n].endpoints[n] is not an " - "object", - KSERR_PARSE); - return CONTINUE; - } - if (!json_object_object_get_ex(endpoint, "versionId", - &endpoint_api_version)) { - /* Keystone documentation includes a versionID key, but it is not - * present in the responses I've seen context->keystone_error( - * "response.access.serviceCatalog[n].endpoints[n] lacks a 'versionId' - * key", KSERR_PARSE); */ - return STOP; /* Take a lack of versionID to mean a catch-all */ - } - if (!json_object_is_type(endpoint_api_version, json_type_string)) { - context->keystone_error( - "response.access.serviceCatalog[n].endpoints[n].versionId is " - "not a string", - KSERR_PARSE); - return CONTINUE; /* Version attribute wrong type */ - } - if (json_object_get_double(endpoint_api_version) != desired_api_version) { - return CONTINUE; /* Not the version we're after */ - } - - /* Found the API version we're after */ - return STOP; -} - -/** - * Given a JSON object representing an OpenStack service, find the first - * endpoint of the given version. If an endpoint of the given version is found, - * return it. Otherwise, if no endpoint of the given version is found, return - * NULL. - */ -static struct json_object * -service_find_endpoint_by_version(keystone_context_t *context, - struct json_object *service, - unsigned int desired_api_version) -{ - struct json_object *endpoints, *endpoint; - - if (json_object_object_get_ex(service, "endpoints", &endpoints)) { - if (0 == desired_api_version) { - /* No desired API version currently set, so use the first endpoint - * found */ - endpoint = json_array_get(context, endpoints, 0); - } else { - /* Looking for a certain version of the Swift RESTful API */ - endpoint = json_array_find(context, endpoints, - filter_endpoint_by_version, - (void *) &desired_api_version); - } - } else { - context->keystone_error("response.access.serviceCatalog[n] lacks an " - "'endpoints' key", - KSERR_PARSE); - endpoint = NULL; /* Lacking the expected key */ - } - - return endpoint; -} - -/** - * Given a JSON object representing an OpenStack service endpoint, return its - * URL of the given type, if any. - * If the service endpoint has no URL - */ -static const char * -endpoint_url(keystone_context_t *context, struct json_object *endpoint, - enum openstack_service_endpoint_url_type endpoint_url_type) -{ - struct json_object *endpoint_public_url; - const char *url_val; - const char *url_type_name; - - assert(context != NULL); - assert(endpoint != NULL); - assert(endpoint_url_type < ELEMENTSOF( - openstack_service_endpoint_url_type_names)); - assert(openstack_service_endpoint_url_type_names - [(unsigned int)endpoint_url_type] != NULL); - - url_type_name = openstack_service_endpoint_url_type_names - [(unsigned int) endpoint_url_type]; - - if (json_object_object_get_ex(endpoint, url_type_name, - &endpoint_public_url)) { - if (json_object_is_type(endpoint_public_url, json_type_string)) { - url_val = json_object_get_string(endpoint_public_url); - } else { - context->keystone_error( - "response.access.serviceCatalog[n].endpoints[n] URL is not " - "a string", - KSERR_PARSE); - url_val = NULL; - } - } else { - context->keystone_error( - "response.access.serviceCatalog[n].endpoints[n] lacks a URL " - "key of the requested type", - KSERR_PARSE); - url_val = NULL; - } - - return url_val; -} - -/** - * Given a desired service type and version and type of URL, find a service of - * the given type in Keystone's catalog of services, then find an endpoint of - * that service with the given API version, then return its URL of the given - * type. Return NULL if the service cannot be found, or if no endpoint of the - * given version can be found, or if the service endpoint of the given version - * has no URL of the given type. - */ -const char * -keystone_get_service_url( - keystone_context_t *context, - enum openstack_service desired_service_type, - unsigned int desired_api_version, - enum openstack_service_endpoint_url_type endpoint_url_type) -{ - struct json_object *service; - const char *url; - - assert(context != NULL); - - service = find_service_by_type(context, context->pvt.services, - desired_service_type); - if (service) { - static struct json_object *endpoint; - endpoint = service_find_endpoint_by_version(context, service, - desired_api_version); - if (endpoint) { - url = endpoint_url(context, endpoint, endpoint_url_type); - } else { - url = NULL; - } - } else { - url = NULL; - } - - return url; -} - -/** - * Retrieve the authentication token and service catalog from a now-complete - * Keystone JSON response, and store them in the Keystone context structure for - * later use. - */ -static enum keystone_error -process_keystone_json(keystone_context_t *context, struct json_object *response) -{ - struct json_object *token; - - if (context->pvt.debug) { - json_object_to_file_ext("/dev/stderr", response, - JSON_C_TO_STRING_PRETTY); - } - if (!json_object_is_type(response, json_type_object)) { - context->keystone_error("response is not an object", KSERR_PARSE); - return KSERR_PARSE; /* Not the expected JSON object */ - } - /* Catalog in "token" -> "catalog" */ - if (!json_object_object_get_ex(response, "token", &token)) { - context->keystone_error("response lacks 'token' key", KSERR_PARSE); - return KSERR_PARSE; /* Lacking the expected key */ - } - if (!json_object_is_type(token, json_type_object)) { - context->keystone_error("response.token is not an object", KSERR_PARSE); - return KSERR_PARSE; /* Not the expected JSON object */ - } - /* Catalog */ - if (!json_object_object_get_ex(token, "catalog", &context->pvt.services)) { - context->keystone_error("response.token lacks 'catalog' key", - KSERR_PARSE); - return KSERR_PARSE; - } - if (!json_object_is_type(context->pvt.services, json_type_array)) { - context->keystone_error("response.token.catalog not an array", - KSERR_PARSE); - return KSERR_PARSE; - } - - return KSERR_SUCCESS; -} - -/** - * Process a Keystone authentication response. - * This parses the response and saves copies of the interesting service endpoint - * URLs. - */ -static size_t -process_keystone_response(void *ptr, size_t size, size_t nmemb, void *userdata) -{ - keystone_context_t *context = (keystone_context_t *) userdata; - const char *body = (const char *) ptr; - size_t len = size * nmemb; - struct json_object *jobj; - enum json_tokener_error json_err; - - assert(context->pvt.json_tokeniser != NULL); - - jobj = json_tokener_parse_ex(context->pvt.json_tokeniser, body, len); - json_err = json_tokener_get_error(context->pvt.json_tokeniser); - if (json_tokener_success == json_err) { - enum keystone_error sc_err = process_keystone_json(context, jobj); - if (sc_err != KSERR_SUCCESS) { - return 0; /* Failed to process JSON. Inform libcurl no data - * 'handled' */ - } - } else if (json_tokener_continue == json_err) { - /* Complete JSON response not yet received; continue */ - } else { - context->json_error("json_tokener_parse_ex", json_err); - context->keystone_error("failed to parse response", KSERR_PARSE); - return 0; /* Apparent JSON parsing problem. Inform libcurl no data - * 'handled' */ - } - - return len; /* Inform libcurl that all data were 'handled' */ -} - static size_t process_keystone_response_headers(char *buffer, size_t size, size_t nitems, void *userdata) { @@ -732,8 +260,7 @@ process_keystone_response_headers(char *buffer, size_t size, size_t nitems, /** * Authenticate against a Keystone authentication service with the given tenant - * and user names and password. This yields an authorisation token, which is - * then used to access all Swift services. + * and user names and password. This yields an authorisation token. */ enum keystone_error keystone_authenticate(keystone_context_t *context, const char *url, @@ -761,18 +288,6 @@ keystone_authenticate(keystone_context_t *context, const char *url, + strlen(KEYSTONE_AUTH_PAYLOAD_END) ; - /* Create or reset the JSON tokeniser */ - if (NULL == context->pvt.json_tokeniser) { - context->pvt.json_tokeniser = json_tokener_new(); - if (NULL == context->pvt.json_tokeniser) { - context->keystone_error("json_tokener_new failed", - KSERR_INIT_FAILED); - return KSERR_INIT_FAILED; - } - } else { - json_tokener_reset(context->pvt.json_tokeniser); - } - curl_err = curl_easy_setopt(context->pvt.curl, CURLOPT_URL, url); if (CURLE_OK != curl_err) { context->curl_error("curl_easy_setopt", curl_err); @@ -840,22 +355,14 @@ keystone_authenticate(keystone_context_t *context, const char *url, headers = curl_slist_append(headers, "Accept: " KEYSTONE_AUTH_RESPONSE_FORMAT); - curl_err = curl_easy_setopt(context->pvt.curl, CURLOPT_HTTPHEADER, headers); + curl_err = curl_easy_setopt(context->pvt.curl, CURLOPT_WRITEFUNCTION, NULL); if (CURLE_OK != curl_err) { context->curl_error("curl_easy_setopt", curl_err); curl_slist_free_all(headers); return KSERR_URL_FAILED; } - curl_err = curl_easy_setopt(context->pvt.curl, CURLOPT_WRITEFUNCTION, - process_keystone_response); - if (CURLE_OK != curl_err) { - context->curl_error("curl_easy_setopt", curl_err); - curl_slist_free_all(headers); - return KSERR_URL_FAILED; - } - - curl_err = curl_easy_setopt(context->pvt.curl, CURLOPT_WRITEDATA, context); + curl_err = curl_easy_setopt(context->pvt.curl, CURLOPT_HTTPHEADER, headers); if (CURLE_OK != curl_err) { context->curl_error("curl_easy_setopt", curl_err); curl_slist_free_all(headers); diff --git a/keystone-client.h b/keystone-client.h index e1a7a30..e20bebc 100644 --- a/keystone-client.h +++ b/keystone-client.h @@ -4,12 +4,11 @@ #include #include #include -#include /** * High-level types of errors which can occur while attempting to use Keystone. - * More detail is available from lower-level libraries (such as curl and - * libjson) using error callbacks specific to those libraries. + * More detail is available from lower-level libraries (such as curl) using + * error callbacks specific to those libraries. */ enum keystone_error { KSERR_SUCCESS = 0, /* Success */ @@ -51,8 +50,6 @@ enum openstack_service_endpoint_url_type { struct keystone_context_private { CURL *curl; /* Handle to curl library's easy interface */ unsigned int debug; - struct json_tokener *json_tokeniser; /* libjson0 library's JSON tokeniser */ - struct json_object *services; /* service catalog JSON array */ unsigned int verify_cert_trusted; /* True if the peer's certificate must * chain to a trusted CA, false otherwise */ unsigned int verify_cert_hostname; /* True if the peer's certificate's @@ -71,10 +68,6 @@ typedef void *(*keystone_allocator_func_t)(void *ptr, size_t newsize); /* A function which receives curl errors */ typedef void (*curl_error_callback_t)(const char *curl_funcname, CURLcode res); -/* A function which receives libjson errors */ -typedef void (*json_error_callback_t)(const char *json_funcname, - enum json_tokener_error json_err); - /* A function which receives Keystone errors */ typedef void (*keystone_error_callback_t)(const char *keystone_operation, enum keystone_error keystone_err); @@ -97,13 +90,6 @@ struct keystone_context { * a default handler will be used. */ curl_error_callback_t curl_error; - /** - * Called when a libjson error occurs. - * Your program may set this function in order to perform custom error - * handling. If this is NULL at the time keystone_start is called, a default - * handler will be used. - */ - json_error_callback_t json_error; /** * Called when a Keystone error occurs. * Your program may set this function in order to perform custom error @@ -222,10 +208,5 @@ const char *keystone_get_auth_token(keystone_context_t *context); * version can be found, or if the service endpoint of the given version has no * URL of the given type. */ -const char *keystone_get_service_url( - keystone_context_t *context, - enum openstack_service desired_service_type, - unsigned int desired_api_version, - enum openstack_service_endpoint_url_type endpoint_url_type); #endif /* KEYSTONE_CLIENT_H_ */ diff --git a/tests.c b/tests/smoke_test.c similarity index 97% rename from tests.c rename to tests/smoke_test.c index 5538b95..bb4b299 100644 --- a/tests.c +++ b/tests/smoke_test.c @@ -1,6 +1,7 @@ +#include #include -#include "keystone-client.h" +#include "../keystone-client.h" enum keystone_error init() { @@ -16,7 +17,6 @@ start(keystone_context_t *context){ /* TODO: eliminate preliminary assignment - do default initialisation * inside keystone_start */ context->curl_error = NULL; - context->json_error = NULL; context->keystone_error = NULL; context->allocator = NULL; enum keystone_error result = keystone_start(context);