diff --git a/README.md b/README.md index bcdc8ad..c933abb 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,27 @@ keystone-client C client for OpenStack Keystone authentication service -Depends on libcurl and libjson-c. +Depends on libcurl. + +Build library +============= +$ sudo apt install libcurl4-openssl-dev + +$ gcc -g3 -c -pedantic -Wall -Wextra -Wformat-security -std=c18 -fpic \ +keystone-client.c -lcurl \ +&& gcc -shared -o libkeystone-client.so keystone-client.o + +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 +$ export KSTEST_ADMIN_USERNAME=admin +$ export KSTEST_ADMIN_PASSWORD=secret + +$ export LD_LIBRARY_PATH=:$LD_LIBRARY_PATH + +$ ./smoke_test diff --git a/keystone-client.c b/keystone-client.c index 0694750..424bd8f 100644 --- a/keystone-client.c +++ b/keystone-client.c @@ -1,6 +1,5 @@ #include #include -#include #include "keystone-client.h" @@ -11,23 +10,21 @@ #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 "\ -{\n\ - \"auth\":{\n\ - \"passwordCredentials\":{\n\ - \"username\":\"" -/* 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\":\"" -/* 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\":\"" -/* The portion of a JSON-encoded Keystone credentials POST body succeeding the tenant name */ -#define KEYSTONE_AUTH_PAYLOAD_END "\"\n\ - }\n\ -}" +/* 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 */ +#define KEYSTONE_AUTH_PAYLOAD_END "\"}}}}" /* Number of elements in a statically-sized array */ #define ELEMENTSOF(arr) (sizeof(arr) / sizeof((arr)[0])) @@ -45,18 +42,9 @@ 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[] = { +/* Human-friendly names for service endpoint URL types */ +static const char *const openstack_service_endpoint_url_type_friendly_names[] = + { "public", "admin", "internal" @@ -69,30 +57,21 @@ 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)); -} - -/** - * 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)); + fprintf(stderr, "%s failed: libcurl error code %ld: %s\n", curl_funcname, + (long) curl_err, curl_easy_strerror(curl_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); } /** @@ -153,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; } @@ -164,11 +140,14 @@ 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; } + context->pvt.auth_token = NULL; + context->pvt.auth_payload = NULL; return KSERR_SUCCESS; } @@ -187,20 +166,18 @@ 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); - } - if (context->pvt.json_tokeniser != NULL) { - json_tokener_free(context->pvt.json_tokeniser); - context->pvt.json_tokeniser = NULL; + context->pvt.auth_payload = context->allocator( + context->pvt.auth_payload, 0); } } /** - * 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) @@ -210,7 +187,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; @@ -220,8 +198,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) @@ -233,7 +212,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; @@ -242,112 +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 (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); - break; - } - } - - return NULL; -} - const char * service_name(unsigned int service) { @@ -358,303 +232,40 @@ 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. - */ -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 *access, *token, *id; - - 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 */ - } - /* 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); - 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); - 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); - 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); - 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; -} - -/** - * 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) -{ +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; - 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' */ + 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 */ } - } 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' */ + strcpy(context->pvt.auth_token, token); + printf("token in process_keystone_response_headers: %s\n", + context->pvt.auth_token); } - - return len; /* Inform libcurl that all data were 'handled' */ + 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. + * Authenticate against a Keystone authentication service with the given tenant + * and user names and password. This yields an authorisation token. */ 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; @@ -677,17 +288,6 @@ keystone_authenticate(keystone_context_t *context, const char *url, const char * + 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); @@ -700,10 +300,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 */ @@ -730,15 +333,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); @@ -746,7 +352,15 @@ 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_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_HTTPHEADER, headers); if (CURLE_OK != curl_err) { @@ -755,14 +369,17 @@ 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_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_WRITEDATA, context); + 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); diff --git a/keystone-client.h b/keystone-client.h index 3d947c6..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 */ @@ -18,7 +17,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 */ }; @@ -50,12 +50,14 @@ 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 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; @@ -66,52 +68,49 @@ 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); +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. - */ - 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 +124,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 +135,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 +149,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 +200,13 @@ 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); #endif /* KEYSTONE_CLIENT_H_ */ diff --git a/tests/smoke_test.c b/tests/smoke_test.c new file mode 100644 index 0000000..bb4b299 --- /dev/null +++ b/tests/smoke_test.c @@ -0,0 +1,106 @@ +#include +#include + +#include "../keystone-client.h" + +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->keystone_error = NULL; + context->allocator = NULL; + enum keystone_error result = keystone_start(context); + printf("keystone_start result: %d\n", result); + return result; +} + +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"); + 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"; + } + enum keystone_error result = keystone_authenticate(context, url, + tenant_name, username, password); + printf("keystone_authenticate result: %d\n", result); + return result; +} + +void print_token (keystone_context_t *context){ + const char *token = keystone_get_auth_token(context); + if (token != NULL) { + printf("token: %s\n", token); + } +} + +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; +} + + +