From 22bc09f578d403fe98c6c51c083b3e7893e65308 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Thu, 13 Sep 2018 22:11:33 +0200 Subject: [PATCH 1/7] add (basic) authentication support - Consider and use the user id and password in DSN: - Feed these params to libcurl. The authentication mechansim is currently set to "any" (Elasticsearch seems to only support the HTTP Basic mechanism). - Change the initial connection testing to actually perform a query ("SELECT 0"), rather then a simple socket connection estiablishemnt check. --- driver/connect.c | 324 +++++++++++++++++++++++++++++----------------- driver/handles.h | 4 +- driver/queries.c | 37 ++---- driver/util.c | 60 +++++++++ driver/util.h | 12 ++ test/test_util.cc | 111 ++++++++++++++++ 6 files changed, 396 insertions(+), 152 deletions(-) create mode 100644 test/test_util.cc diff --git a/driver/connect.c b/driver/connect.c index df8e8b28..ed327536 100644 --- a/driver/connect.c +++ b/driver/connect.c @@ -20,6 +20,7 @@ /* HTTP headers default for every request */ #define HTTP_ACCEPT_JSON "Accept: application/json" #define HTTP_CONTENT_TYPE_JSON "Content-Type: application/json; charset=utf-8" +#define HTTP_TEST_JSON "{\"query\": \"SELECT 0\"}" /* Elasticsearch/SQL data types */ /* 4 */ @@ -225,11 +226,10 @@ static SQLRETURN init_curl(esodbc_dbc_st *dbc) #endif /* NDEBUG */ /* set URL to connect to */ - res = curl_easy_setopt(curl, CURLOPT_URL, dbc->url); + res = curl_easy_setopt(curl, CURLOPT_URL, dbc->url.str); if (res != CURLE_OK) { - ERRH(dbc, "libcurl: failed to set URL `%s`: %s (%d).", dbc->url, + ERRH(dbc, "libcurl: failed to set URL `%s`: %s (%d).", dbc->url.str, curl_easy_strerror(res), res); - SET_HDIAG(dbc, SQL_STATE_HY000, "failed to init the transport", 0); goto err; } /* always do POSTs (seconded by CURLOPT_POSTFIELDS) */ @@ -237,7 +237,6 @@ static SQLRETURN init_curl(esodbc_dbc_st *dbc) if (res != CURLE_OK) { ERRH(dbc, "libcurl: failed to set POST method: %s (%d).", curl_easy_strerror(res), res); - SET_HDIAG(dbc, SQL_STATE_HY000, "failed to init the transport", 0); goto err; } @@ -246,7 +245,6 @@ static SQLRETURN init_curl(esodbc_dbc_st *dbc) if (res != CURLE_OK) { ERRH(dbc, "libcurl: failed to set headers: %s (%d).", curl_easy_strerror(res), res); - SET_HDIAG(dbc, SQL_STATE_HY000, "failed to init the transport", 0); goto err; } @@ -255,16 +253,46 @@ static SQLRETURN init_curl(esodbc_dbc_st *dbc) if (res != CURLE_OK) { ERRH(dbc, "libcurl: failed to set redirection: %s (%d).", curl_easy_strerror(res), res); - SET_HDIAG(dbc, SQL_STATE_HY000, "failed to init the transport", 0); goto err; } + if (dbc->uid.cnt) { + /* set the authentication methods: + * "basic" is currently - 7.0.0 - the only supported method */ + res = curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY); + if (res != CURLE_OK) { + ERRH(dbc, "libcurl: failed to set HTTP auth methods: %s (%d).", + curl_easy_strerror(res), res); + goto err; + } + /* set the username */ + res = curl_easy_setopt(curl, CURLOPT_USERNAME, dbc->uid.str); + if (res != CURLE_OK) { + ERRH(dbc, "libcurl: failed to set auth username: %s (%d).", + curl_easy_strerror(res), res); + goto err; + } + /* set the password */ + if (dbc->pwd.cnt) { + res = curl_easy_setopt(curl, CURLOPT_PASSWORD, dbc->pwd.str); + if (res != CURLE_OK) { + ERRH(dbc, "libcurl: failed to set auth password: %s (%d).", + curl_easy_strerror(res), res); + goto err; + } + } else { + INFOH(dbc, "no password for username `" LCPDL "`.", + LCSTR(&dbc->uid)); + } + } else { + INFOH(dbc, "no username provided: auth disabled."); + } + /* set the write call-back for answers */ res = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); if (res != CURLE_OK) { ERRH(dbc, "libcurl: failed to set write callback: %s (%d).", curl_easy_strerror(res), res); - SET_HDIAG(dbc, SQL_STATE_HY000, "failed to init the transport", 0); goto err; } /* ... and its argument */ @@ -272,7 +300,6 @@ static SQLRETURN init_curl(esodbc_dbc_st *dbc) if (res != CURLE_OK) { ERRH(dbc, "libcurl: failed to set callback argument: %s (%d).", curl_easy_strerror(res), res); - SET_HDIAG(dbc, SQL_STATE_HY000, "failed to init the transport", 0); goto err; } @@ -281,10 +308,9 @@ static SQLRETURN init_curl(esodbc_dbc_st *dbc) return SQL_SUCCESS; err: - if (curl) { - curl_easy_cleanup(curl); - } - RET_STATE(dbc->hdr.diag.state); + assert(curl); + curl_easy_cleanup(curl); + RET_HDIAG(dbc, SQL_STATE_HY000, "failed to init the transport", 0); } static void cleanup_curl(esodbc_dbc_st *dbc) @@ -297,139 +323,173 @@ static void cleanup_curl(esodbc_dbc_st *dbc) dbc->curl = NULL; } -/* - * Sends a POST request with the given JSON object body. - */ -SQLRETURN post_json(esodbc_stmt_st *stmt, const cstr_st *u8body) +static CURLcode dbc_execute(esodbc_dbc_st *dbc, long *code, cstr_st *resp) { - // char *const answer, /* buffer to receive the answer in */ - // long avail /*size of the answer buffer */) - CURLcode res = CURLE_OK; - esodbc_dbc_st *dbc = stmt->hdr.dbc; - char *abuff = NULL; - size_t apos; - SQLULEN tout; - long code; + CURLcode res; - DBGH(stmt, "POSTing JSON [%zd] `" LCPDL "`.", u8body->cnt, LCSTR(u8body)); + assert(dbc->abuff == NULL); - ESODBC_MUX_LOCK(&dbc->curl_mux); + /* execute the request */ + res = curl_easy_perform(dbc->curl); - if (! dbc->curl) { - init_curl(dbc); + /* copy answer references */ + resp->str = dbc->abuff; + resp->cnt = dbc->apos; + + /* clear call-back members for next call */ + dbc->abuff = NULL; + dbc->apos = 0; + dbc->alen = 0; + + if (res != CURLE_OK) { + return res; + } + res = curl_easy_getinfo(dbc->curl, CURLINFO_RESPONSE_CODE, code); + if (res != CURLE_OK) { + return res; } - /* set timeout as maximum between connection and statement value */ - tout = dbc->timeout < stmt->query_timeout ? stmt->query_timeout : - dbc->timeout; + DBGH(dbc, "libcurl: request answered, received code %ld and %zd bytes" + " back.", *code, resp->cnt); + + return CURLE_OK; +} + +static CURLcode dbc_prepare_post(esodbc_dbc_st *dbc, SQLULEN tout, + const cstr_st *u8body) +{ + CURLcode res; + if (0 < tout) { + DBGH(dbc, "libcurl: setting timeout: %ld.", tout); res = curl_easy_setopt(dbc->curl, CURLOPT_TIMEOUT, tout); if (res != CURLE_OK) { - goto err; - } else { - DBGH(stmt, "libcurl: set curl 0x%p timeout to %ld.", dbc->curl, - tout); + return res; } } /* len of the body */ + DBGH(dbc, "libcurl: setting post fieldsize: %ld.", u8body->cnt); res = curl_easy_setopt(dbc->curl, CURLOPT_POSTFIELDSIZE, u8body->cnt); if (res != CURLE_OK) { - goto err; - } else { - DBGH(stmt, "libcurl: set curl 0x%p post fieldsize to %ld.", - dbc->curl, u8body->cnt); + return res; } /* body itself */ + DBGH(dbc, "libcurl: setting post fields to `" LCPDL "`.", LCSTR(u8body)); res = curl_easy_setopt(dbc->curl, CURLOPT_POSTFIELDS, u8body->str); if (res != CURLE_OK) { - goto err; - } else { - DBGH(stmt, "libcurl: set curl 0x%p post fields to `" LCPDL "`.", - dbc->curl, LCSTR(u8body)); + return res; } - assert(dbc->abuff == NULL); + return CURLE_OK; +} - /* execute the request */ - res = curl_easy_perform(dbc->curl); +/* + * Sends a POST request with the given JSON object body. + */ +SQLRETURN post_json(esodbc_stmt_st *stmt, const cstr_st *u8body) +{ + // char *const answer, /* buffer to receive the answer in */ + // long avail /*size of the answer buffer */) + CURLcode res = CURLE_OK; + esodbc_dbc_st *dbc = stmt->hdr.dbc; + SQLULEN tout; + long code; + cstr_st resp = (cstr_st) { + NULL, 0 + }; - abuff = dbc->abuff; - apos = dbc->apos; + DBGH(stmt, "POSTing JSON [%zd] `" LCPDL "`.", u8body->cnt, LCSTR(u8body)); - /* clear call-back members for next call */ - dbc->abuff = NULL; - dbc->apos = 0; - dbc->alen = 0; + ESODBC_MUX_LOCK(&dbc->curl_mux); + + if (! dbc->curl) { + init_curl(dbc); + } + /* set timeout as maximum between connection and statement value */ + tout = dbc->timeout < stmt->query_timeout ? stmt->query_timeout : + dbc->timeout; + + res = dbc_prepare_post(dbc, tout, u8body); if (res != CURLE_OK) { goto err; } - res = curl_easy_getinfo(dbc->curl, CURLINFO_RESPONSE_CODE, &code); + + res = dbc_execute(dbc, &code, &resp); if (res != CURLE_OK) { goto err; } - DBGH(stmt, "libcurl: request succesfull, received code %ld and %zd bytes" - " back.", code, apos); - ESODBC_MUX_UNLOCK(&dbc->curl_mux); + /* expect a 200 with body; everything else is failure */ + if (code == 200 && resp.cnt) { + ESODBC_MUX_UNLOCK(&dbc->curl_mux); + + assert(resp.str); + return attach_answer(stmt, resp.str, resp.cnt); + } else { + cleanup_curl(dbc); + ESODBC_MUX_UNLOCK(&dbc->curl_mux); - if (code != 200) { - ERRH(stmt, "libcurl: non-200 HTTP response code %ld received.", code); - /* expect a 200 with body; everything else is failure (todo?) */ - if (400 <= code) { - return attach_error(stmt, abuff, apos); + ERRH(stmt, "libcurl: answer code: %ld, answer lenght: %zd " + "(non 200 HTTP response with body).", code, resp.cnt); + if (resp.cnt) { + assert(resp.str); + return attach_error(stmt, resp.str, resp.cnt); } - goto err_net; + goto err_net; /* skip unlocking */ } - return attach_answer(stmt, abuff, apos); - err: ERRH(stmt, "libcurl: request failed (timeout:%llu, body:`" LCPDL "`) " "failed: '%s' (%d).", tout, LCSTR(u8body), res != CURLE_OK ? curl_easy_strerror(res) : "", res); -err_net: /* the error occured after the request hit hit the network */ cleanup_curl(dbc); ESODBC_MUX_UNLOCK(&dbc->curl_mux); - if (abuff) { - free(abuff); - abuff = NULL; - /* if buffer had been set, the error occured in _perform() */ +err_net: /* error occured after network xfer; lock is freed */ + if (resp.str) { + free(resp.str); + resp.str = NULL; + /* if buffer had been set, the error occured at/after _perform() */ RET_HDIAG(stmt, SQL_STATE_08S01, "data transfer failure", res); } RET_HDIAG(stmt, SQL_STATE_HY000, "failed to init transport", res); } -static SQLRETURN test_connect(CURL *curl) +static SQLRETURN test_connect(esodbc_dbc_st *dbc) { CURLcode res; + long code = -1; + cstr_st u8body = MK_CSTR(HTTP_TEST_JSON); + cstr_st resp = (cstr_st) { + NULL, 0 + }; - /* we only one to connect */ - res = curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 1L); - if (res != CURLE_OK) { - ERR("libcurl: failed to set connect_only: %s (%d).", - curl_easy_strerror(res), res); - return SQL_ERROR; - } - - res = curl_easy_perform(curl); + res = dbc_prepare_post(dbc, dbc->timeout, &u8body); if (res != CURLE_OK) { - ERR("libcurl: failed connect: %s (%d).", - curl_easy_strerror(res), res); - return SQL_ERROR; + goto err; } - res = curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 0L); - if (res != CURLE_OK) { - ERR("libcurl: failed to unset connect_only: %s (%d).", - curl_easy_strerror(res), res); - return SQL_ERROR; + res = dbc_execute(dbc, &code, &resp); + if (res != CURLE_OK || code != 200) { + goto err; } - DBG("successfully connected to server."); + DBGH(dbc, "test connection succesful."); return SQL_SUCCESS; + +err: + ERRH(dbc, "libcurl: test connection failed: %s (%d).", + curl_easy_strerror(res), res); + if (0 < code) { + ERRH(dbc, "libcurl: test connection code: %ld.", code); + } + if (resp.cnt) { + ERRH(dbc, "libcurl: test connection answer: `" LCPDL "`.", + LCSTR(&resp)); + } + return SQL_ERROR; } /* @@ -438,17 +498,20 @@ static SQLRETURN test_connect(CURL *curl) static SQLRETURN process_config(esodbc_dbc_st *dbc, esodbc_dsn_attrs_st *attrs) { esodbc_state_et state = SQL_STATE_HY000; - int n, cnt; - SQLWCHAR urlw[ESODBC_MAX_URL_LEN]; + int cnt; BOOL secure; long long timeout, max_body_size, max_fetch_size; + SQLWCHAR buff_url[ESODBC_MAX_URL_LEN]; + wstr_st url = (wstr_st) { + buff_url, /*will be init'ed later*/0 + }; /* - * build connection URL + * URL of the cluster */ secure = wstr2bool(&attrs->secure); INFOH(dbc, "connect secure: %s.", secure ? "true" : "false"); - cnt = swprintf(urlw, sizeof(urlw)/sizeof(urlw[0]), + cnt = swprintf(url.str, sizeof(buff_url)/sizeof(*buff_url), L"http" WPFCP_DESC "://" WPFWP_LDESC ":" WPFWP_LDESC ELASTIC_SQL_PATH, secure ? "s" : "", LWSTR(&attrs->server), LWSTR(&attrs->port)); @@ -457,31 +520,34 @@ static SQLRETURN process_config(esodbc_dbc_st *dbc, esodbc_dsn_attrs_st *attrs) "port: `" LWPDL "` [%zd].", LWSTR(&attrs->server), LWSTR(&attrs->port)); goto err; + } else { + url.cnt = (size_t)cnt; } - /* length of URL converted to U8 */ - n = WCS2U8(urlw, cnt, NULL, 0); - if (! n) { - ERRNH(dbc, "failed to estimate U8 conversion space necessary for `" - LWPDL " [%d]`.", cnt, urlw, cnt); - goto err; - } - dbc->url = malloc(n + /*0-term*/1); - if (! dbc->url) { - ERRNH(dbc, "OOM for size: %dB.", n); - state = SQL_STATE_HY001; + if (! wstr_to_utf8(&url, &dbc->url)) { + ERRNH(dbc, "failed to convert URL `" LWPDL "` to UTF8.", LWSTR(&url)); goto err; } - n = WCS2U8(urlw, cnt, dbc->url, n); - if (! n) { - ERRNH(dbc, "failed to U8 convert URL `" LWPDL "` [%d].",cnt, urlw, - cnt); - goto err; + INFOH(dbc, "connection URL: `%s`.", dbc->url.str); + + /* + * credentials + */ + if (attrs->uid.cnt) { + if (! wstr_to_utf8(&attrs->uid, &dbc->uid)) { + ERRH(dbc, "failed to convert username [%zd] `" LWPDL "` to UTF8.", + attrs->uid.cnt, LWSTR(&attrs->uid)); + goto err; + } + if (attrs->pwd.cnt) { + if (! wstr_to_utf8(&attrs->pwd, &dbc->pwd)) { + ERRH(dbc, "failed to convert username [%zd] `" LWPDL "` to " + "UTF8.", attrs->pwd.cnt, LWSTR(&attrs->pwd)); + goto err; + } + } } - dbc->url[n] = 0; - /* URL should be 0-term'd, as printed by swprintf */ - INFOH(dbc, "connection URL: `%s`.", dbc->url); - /* follow param for liburl */ + /* "follow location" param for liburl */ dbc->follow = wstr2bool(&attrs->follow); INFOH(dbc, "follow: %s.", dbc->follow ? "true" : "false"); @@ -575,9 +641,20 @@ static SQLRETURN process_config(esodbc_dbc_st *dbc, esodbc_dsn_attrs_st *attrs) /* release all resources, except the handler itself */ void cleanup_dbc(esodbc_dbc_st *dbc) { - if (dbc->url) { - free(dbc->url); - dbc->url = NULL; + if (dbc->url.str) { + free(dbc->url.str); + dbc->url.str = NULL; + dbc->url.cnt = 0; + } + if (dbc->uid.str) { + free(dbc->uid.str); + dbc->uid.str = NULL; + dbc->uid.cnt = 0; + } + if (dbc->pwd.str) { + free(dbc->pwd.str); + dbc->pwd.str = NULL; + dbc->pwd.cnt = 0; } if (dbc->fetch.str) { free(dbc->fetch.str); @@ -626,15 +703,18 @@ static SQLRETURN do_connect(esodbc_dbc_st *dbc, esodbc_dsn_attrs_st *attrs) return ret; } - /* perform a connection test, to fail quickly if wrong server/port AND - * populate the DNS cache; this won't guarantee succesful post'ing, tho! */ - ret = test_connect(dbc->curl); + /* perform a connection test, to fail quickly if wrong params AND + * populate the DNS cache */ + ret = test_connect(dbc); /* still ok if fails */ - curl_easy_getinfo(dbc->curl, CURLINFO_EFFECTIVE_URL, &url); + if (curl_easy_getinfo(dbc->curl, CURLINFO_EFFECTIVE_URL, &url) != + CURLE_OK) { + url = ""; + } if (! SQL_SUCCEEDED(ret)) { - ERRH(dbc, "test connection to URL %s failed!", url); + ERRH(dbc, "test connection to URL `%s` failed!", url); cleanup_curl(dbc); - RET_HDIAG(dbc, SQL_STATE_HYT01, "connection test failed", 0); + RET_HDIAGS(dbc, SQL_STATE_08001); } else { DBGH(dbc, "test connection to URL %s OK.", url); } @@ -1630,7 +1710,7 @@ SQLRETURN EsSQLSetConnectAttrW( case SQL_ATTR_TXN_ISOLATION: DBGH(dbc, "attempt to set transaction isolation to: %u.", (SQLUINTEGER)(uintptr_t)Value); - ERRH(dbc, "no support for transactions available."); + WARNH(dbc, "no support for transactions available."); /* the driver advertises the data source as read-only, so no * transaction level setting should occur. If an app seems to rely * on it, we need to switch from ignoring the action to rejecting diff --git a/driver/handles.h b/driver/handles.h index afd3a505..8c01f56d 100644 --- a/driver/handles.h +++ b/driver/handles.h @@ -127,7 +127,9 @@ typedef struct struct_dbc { wstr_st dsn; /* data source name SQLGetInfo(SQL_DATA_SOURCE_NAME) */ wstr_st server; /* ~ name; requested with SQLGetInfo(SQL_SERVER_NAME) */ - char *url; + cstr_st url; + cstr_st uid; + cstr_st pwd; SQLUINTEGER timeout; BOOL follow; struct { diff --git a/driver/queries.c b/driver/queries.c index 2be3893a..15b193d9 100644 --- a/driver/queries.c +++ b/driver/queries.c @@ -407,39 +407,18 @@ SQLRETURN TEST_API attach_sql(esodbc_stmt_st *stmt, const SQLWCHAR *sql, /* SQL text statement */ size_t sqlcnt /* count of chars of 'sql' */) { - char *u8; - int len; + wstr_st sqlw = (wstr_st) { + (SQLWCHAR *)sql, sqlcnt + }; - DBGH(stmt, "attaching SQL `" LWPDL "` (%zd).", sqlcnt, sql, sqlcnt); + DBGH(stmt, "attaching SQL [%zd] `" LWPDL "`.", sqlcnt, LWSTR(&sqlw)); - len = WCS2U8(sql, (int)sqlcnt, NULL, 0); - if (len <= 0) { - ERRNH(stmt, "failed to UCS2/UTF8 convert SQL `" LWPDL "` (%zd).", - sqlcnt, sql, sqlcnt); + assert(! stmt->u8sql.str); + if (! wstr_to_utf8(&sqlw, &stmt->u8sql)) { + ERRNH(stmt, "conversion UCS2->UTF8 of SQL [%zu] `" LWPDL "` failed.", + sqlcnt, LWSTR(&sqlw)); RET_HDIAG(stmt, SQL_STATE_HY000, "UCS2/UTF8 conversion failure", 0); } - DBGH(stmt, "wide char SQL `" LWPDL "`[%zd] converts to UTF8 on %d " - "octets.", sqlcnt, sql, sqlcnt, len); - - u8 = malloc(len); - if (! u8) { - ERRNH(stmt, "failed to alloc %dB.", len); - RET_HDIAGS(stmt, SQL_STATE_HY001); - } - - len = WCS2U8(sql, (int)sqlcnt, u8, len); - if (len <= 0) { /* can it happen? it's just succeded above */ - ERRNH(stmt, "failed to UCS2/UTF8 convert SQL `" LWPDL "` (%zd).", - sqlcnt, sql, sqlcnt); - free(u8); - RET_HDIAG(stmt, SQL_STATE_HY000, "UCS2/UTF8 conversion failure(2)", 0); - } - - assert(! stmt->u8sql.str); - stmt->u8sql.str = u8; - stmt->u8sql.cnt = (size_t)len; - - DBGH(stmt, "attached SQL `%.*s` (%zd).", len, u8, len); return SQL_SUCCESS; } diff --git a/driver/util.c b/driver/util.c index 360ce42f..98d7db5a 100644 --- a/driver/util.c +++ b/driver/util.c @@ -503,4 +503,64 @@ SQLRETURN write_wstr(SQLHANDLE hnd, SQLWCHAR *dest, wstr_st *src, return SQL_SUCCESS; } +cstr_st TEST_API *wstr_to_utf8(wstr_st *src, cstr_st *dst) +{ + int len; + size_t cnt; + void *addr; + BOOL nts; /* is the \0 present and counted in source string? */ + + if (0 < src->cnt) { + nts = !src->str[src->cnt - 1]; + + /* eval the needed space for conversion */ + len = WCS2U8(src->str, (int)src->cnt, NULL, 0); + if (! len) { + ERRN("failed to evaluate UTF-8 conversion space necessary for [%zu] " + "`" LWPDL "`.", src->cnt, LWSTR(src)); + return NULL; + } + } else { + nts = FALSE; + len = 0; + } + + assert(0 <= len); + /* explicitely allocate the \0 if not present&counted */ + cnt = len + /*0-term?*/!nts; + if (! dst) { /* if null destination, allocate that as well */ + cnt += sizeof(cstr_st); + } + + if (! (addr = malloc(cnt))) { + ERRN("OOM for size: %zuB.", cnt); + return NULL; + } + if (! dst) { + dst = (cstr_st *)addr; + dst->str = (uint8_t *)addr + sizeof(cstr_st); + } else { + dst->str = (SQLCHAR *)addr; + } + + if (0 < src->cnt) { + /* convert the string */ + len = WCS2U8(src->str, (int)src->cnt, dst->str, len); + if (! len) { + /* should not happen, since a first scan already happened */ + ERRN("failed to UTF-8 convert `" LWPDL "`.", LWSTR(src)); + free(addr); + return NULL; + } + } + + if (! nts) { + dst->str[len] = 0; + } + dst->cnt = len; + + return dst; +} + + /* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : */ diff --git a/driver/util.h b/driver/util.h index 56c28775..5d8bb0e4 100644 --- a/driver/util.h +++ b/driver/util.h @@ -239,6 +239,18 @@ size_t json_escape_overlapping(char *str, size_t inlen, size_t outlen); SQLRETURN write_wstr(SQLHANDLE hnd, SQLWCHAR *dest, wstr_st *src, SQLSMALLINT /*B*/avail, SQLSMALLINT /*B*/*usedp); +/* + * Converts a wide string to a UTF-8 MB, allocating the necessary space. + * The \0 is allocated and written, even if not present in source string, but + * only counted in output string if counted in input one. + * If 'dst' is null, the destination is also going to be allocate (collated + * with the string). The caller only needs to free the allocated chunk + * (returned pointer or dst->str). + * Returns NULL on error. + */ +//cstr_st* TEST_API wstr_to_utf8(wstr_st *src, cstr_st *dst); +cstr_st TEST_API *wstr_to_utf8(wstr_st *src, cstr_st *dst); + /* * Printing aids. */ diff --git a/test/test_util.cc b/test/test_util.cc new file mode 100644 index 00000000..4574ae2c --- /dev/null +++ b/test/test_util.cc @@ -0,0 +1,111 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +extern "C" { +#include "util.h" +} // extern C + +#include + + +namespace test { + +class Util : public ::testing::Test { +}; + +TEST_F(Util, wstr_to_utf8_null_dst) { +#undef SRC_STR +#define SRC_STR "abcd" + wstr_st src = WSTR_INIT(SRC_STR); + cstr_st *dst; + + dst = wstr_to_utf8(&src, NULL); + ASSERT_TRUE(dst != NULL); + ASSERT_EQ(dst->cnt, sizeof(SRC_STR) - 1); + ASSERT_EQ(dst->str[dst->cnt], '\0'); + ASSERT_STREQ((char *)SRC_STR, (char *)dst->str); + free(dst); +} + +TEST_F(Util, wstr_to_utf8_empty_src) { +#undef SRC_STR +#define SRC_STR "" + wstr_st src = WSTR_INIT(SRC_STR); + cstr_st *dst; + + dst = wstr_to_utf8(&src, NULL); + ASSERT_TRUE(dst != NULL); + ASSERT_EQ(dst->cnt, sizeof(SRC_STR) - 1); + ASSERT_EQ(dst->str[dst->cnt], '\0'); + ASSERT_STREQ((char *)SRC_STR, (char *)dst->str); + free(dst); +} + +TEST_F(Util, wstr_to_utf8_unicode) { +#undef SRC_STR +#undef SRC_AID +#define SRC_STR "XäXüXßX" +#define SRC_AID "X\xC3\xA4X\xC3\xBCX\xC3\x9FX" + wstr_st src; + cstr_st dst; + src.str = MK_WPTR(SRC_STR); + src.cnt = sizeof(MK_WPTR(SRC_STR))/sizeof(SQLWCHAR) - 1; + + ASSERT_EQ(&dst, wstr_to_utf8(&src, &dst)); + ASSERT_EQ(dst.cnt, sizeof(SRC_AID) - 1); + ASSERT_EQ(dst.str[dst.cnt], '\0'); + ASSERT_STREQ((char *)SRC_STR, (char *)dst.str); + free(dst.str); +} + +TEST_F(Util, wstr_to_utf8_nts_not_counted) { +#undef SRC_STR +#define SRC_STR "abcd" + wstr_st src = WSTR_INIT(SRC_STR); + cstr_st dst; + + ASSERT_EQ(&dst, wstr_to_utf8(&src, &dst)); + ASSERT_EQ(dst.cnt, sizeof(SRC_STR) - 1); + ASSERT_EQ(dst.str[dst.cnt], '\0'); + ASSERT_STREQ((char *)SRC_STR, (char *)dst.str); + free(dst.str); +} + +TEST_F(Util, wstr_to_utf8_nts_counted) { +#undef SRC_STR +#define SRC_STR "abcd" + wstr_st src = WSTR_INIT(SRC_STR); + src.cnt ++; + cstr_st dst; + + ASSERT_EQ(&dst, wstr_to_utf8(&src, &dst)); + ASSERT_EQ(dst.cnt, sizeof(SRC_STR)); + ASSERT_EQ(dst.str[dst.cnt - 1], '\0'); + ASSERT_STREQ((char *)SRC_STR, (char *)dst.str); + free(dst.str); +} + +TEST_F(Util, wstr_to_utf8_no_nts) { +#undef SRC_STR +#undef SRC_AID +#define SRC_AID "abcd" +#define SRC_STR "XXX" SRC_AID "XXX" + wstr_st src = WSTR_INIT(SRC_STR); + src.str += 3; /*`XXX`*/ + src.cnt = sizeof(SRC_AID) - 1; + cstr_st dst; + + ASSERT_EQ(&dst, wstr_to_utf8(&src, &dst)); + ASSERT_EQ(dst.cnt, sizeof(SRC_AID) - 1); + ASSERT_EQ(dst.str[dst.cnt], '\0'); + ASSERT_STREQ((char *)SRC_AID, (char *)dst.str); + free(dst.str); +} + + +} // test namespace + +/* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : */ From c5570c2bc7e56edd9772d24c4ecfc8a27a8fd300 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Thu, 13 Sep 2018 22:16:28 +0200 Subject: [PATCH 2/7] prefix build.bat messages by its name This should allow easier spotting of the build phases --- build.bat | 65 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/build.bat b/build.bat index b5dce0c6..ef9c0761 100644 --- a/build.bat +++ b/build.bat @@ -54,7 +54,7 @@ if /i not [%ARG:setup=%] == [%ARG%] ( where cl.exe >nul 2>&1 if ERRORLEVEL 1 ( echo. - echo ERROR: building environment not set. Run with /? to see options. + echo %~nx0: ERROR: building environment not set. Run with /? to see options. echo. goto END ) @@ -80,7 +80,7 @@ REM presence of 'install'/'package': invoke respective _CONF "function" if /i not [%ARG:install=%] == [%ARG%] ( if /i not [%ARG:package=%] == [%ARG%] ( REM Since CPACK uses the install target: - echo ERROR: 'install' and 'package' are mutually exclusive actions. + echo %~nx0: ERROR: 'install' and 'package' are mutually exclusive actions. exit /b 1 ) call:INSTALL_CONF @@ -94,7 +94,7 @@ if /i not [%ARG:type=%] == [%ARG%] ( ) else ( set BUILD_TYPE=Debug set MSBUILD_ARGS=/p:Configuration=!BUILD_TYPE! - echo Invoked without 'type', !BUILD_TYPE!-building (default^). + echo %~nx0: invoked without 'type', !BUILD_TYPE!-building (default^). ) REM absence of nobuild: invoke BUILD "function"; @@ -112,7 +112,7 @@ if /i [%ARG:nobuild=%] == [%ARG%] ( goto END ) ) else ( - echo Invoked with 'nobuild', building skipped. + echo %~nx0: invoked with 'nobuild', building skipped. ) @@ -187,15 +187,16 @@ REM function to check and set cmake binary (if installed) REM Using already set environment path ) else ( echo. - echo ERROR: needed cmake executable not found: when installed, - echo either set it in PATH or in environment variable CMAKE + echo %~nx0: ERROR: needed cmake executable not found: when + echo installed, either set it in PATH or in + echo environment variable CMAKE. echo. goto END ) ) else ( set CMAKE=cmake.exe ) - echo|set /p="Using CMAKE binary: %CMAKE% : " + echo|set /p="%~nx0: using CMAKE binary: %CMAKE% : " %CMAKE% --version | findstr /C:"version" goto:eof @@ -259,7 +260,7 @@ REM USAGE function: output a usage message REM PROPER function: clean up the build and libs dir. :PROPER - echo Cleaning libs. + echo %~nx0: cleaning libs. if exist %BUILD_DIR%\curlclean.vcxproj ( MSBuild %BUILD_DIR%\curlclean.vcxproj ) @@ -274,7 +275,7 @@ REM PROPER function: clean up the build and libs dir. REM CLEAN function: clean up the build dir. :CLEAN - echo Cleaning builds. + echo %~nx0: cleaning builds. REM delete all files that don't have the "extension" .gitignore :-) for %%i in (%BUILD_DIR%\*) do if not %%~xi == .gitignore ( del /s /q %%i >nul 2>&1 @@ -292,7 +293,7 @@ REM SETUP function: set-up the build environment if exist "C:\Program Files (x86)\Microsoft Visual Studio\%RELEASE%\%%e\Common7\Tools\VsDevCmd.bat" ( if /i "%%e" == "Community" ( echo. - echo WARNING: Community edition is not licensed to build commerical projects. + echo %~nx0: WARNING: Community edition is not licensed to build commerical projects. echo. ) call "C:\Program Files (x86)\Microsoft Visual Studio\%RELEASE%\%%e\Common7\Tools\VsDevCmd.bat" -arch=!TARCH! @@ -302,7 +303,7 @@ REM SETUP function: set-up the build environment ) if [%EDITION%] == [] ( echo. - echo WARNING: no MSVC edition found, environment not set. + echo %~nx0: WARNING: no MSVC edition found, environment not set. echo. ) @@ -312,11 +313,11 @@ REM INSTALL_CONF function: extract install location, if any; this will be REM injected into the project files generated by cmake :INSTALL_CONF if exist ALL_BUILD.vcxproj ( - echo Project files already generated, install configuration skipped. + echo %~nx0: NOTICE: project files already generated, install configuration skipped. goto:eof ) if not [%INSTALL_DIR%] == [] ( - echo Installation directory set by INSTALL_DIR=%INSTALL_DIR% + echo %~nx0: installation directory set by INSTALL_DIR=%INSTALL_DIR%. goto:eof ) @@ -326,7 +327,7 @@ REM injected into the project files generated by cmake set crr=%%a if /i ["!crr:~0,8!"] == ["install:"] ( set INSTALL_DIR=!crr:~8! - echo Setting the installation dir to: !INSTALL_DIR! + echo %~nx0: Setting the installation dir to: !INSTALL_DIR!. goto:eof ) ) @@ -338,7 +339,7 @@ REM PACKAGE_CONF function: extract versioning string, if any; this will be REM injected into the project files generated by cmake :PACKAGE_CONF if exist ALL_BUILD.vcxproj ( - echo Project files already generated, package configuration skipped. + echo %~nx0: NOTICE: project files already generated, package configuration skipped. goto:eof ) @@ -348,7 +349,7 @@ REM injected into the project files generated by cmake set crr=%%a if /i ["!crr:~0,8!"] == ["package:"] ( set PACKAGE_VER=!crr:~8! - echo Setting the packaging version string to: !PACKAGE_VER! + echo %~nx0: setting the packaging version string to: !PACKAGE_VER!. REM CPACK needs a relative prefix in order to install the files REM correctly. @@ -369,7 +370,7 @@ REM BUILDTYPE function: set the build config to feed MSBuild if /i ["!crr:~0,5!"] == ["type:"] ( set BUILD_TYPE=!crr:~5! set MSBUILD_ARGS=/p:Configuration=!BUILD_TYPE! - echo Setting the build type to: !MSBUILD_ARGS! + echo %~nx0: setting the build type to: !MSBUILD_ARGS!. goto:eof ) ) @@ -380,7 +381,7 @@ REM BUILDTYPE function: set the build config to feed MSBuild REM BUILD function: build various targets :BUILD if not exist ALL_BUILD.vcxproj ( - echo Generating the project files. + echo %~nx0: generating the project files. set CMAKE_ARGS=-DDRIVER_BASE_NAME=%DRIVER_BASE_NAME% REM no explicit x86 generator and is the default (MSVC2017 only?). @@ -392,7 +393,7 @@ REM BUILD function: build various targets set CMAKE_ARGS=!CMAKE_ARGS! -DVERSION_QUALIFIER=!PACKAGE_VER! ) - echo cmake params: !CMAKE_ARGS! + echo %~nx0: cmake params: !CMAKE_ARGS!. %CMAKE% !CMAKE_ARGS! !SRC_PATH! ) if /i not [%ARG:genonly=%] == [%ARG%] ( @@ -400,13 +401,13 @@ REM BUILD function: build various targets ) if /i not [%ARG:tests=%] == [%ARG%] ( - echo Building all the project. + echo %~nx0: building all the project. MSBuild ALL_BUILD.vcxproj %MSBUILD_ARGS% if ERRORLEVEL 1 ( goto END ) ) else ( - echo Building the driver. + echo %~nx0: building the driver. REM file name expansion, cmd style... for /f %%i in ("%DRIVER_BASE_NAME%*.vcxproj") do ( MSBuild %%~nxi %MSBUILD_ARGS% @@ -422,7 +423,7 @@ REM BUILD function: build various targets ) if /i not [%ARG:suiteS=%] == [%ARG%] ( - echo Building the test projects. + echo %~nx0: building the test projects. for %%i in (test\test_*.vcxproj) do ( MSBuild %%~fi %MSBUILD_ARGS% if ERRORLEVEL 1 ( @@ -436,7 +437,7 @@ REM BUILD function: build various targets set crr=%%a if /i ["!crr:~0,6!"] == ["suite:"] ( set SUITE=!crr:~6! - echo Building one suite: !SUITE! + echo %~nx0: building one suite: !SUITE!. MSBuild test\!SUITE!.vcxproj %MSBUILD_ARGS% if ERRORLEVEL 1 ( goto END @@ -457,9 +458,9 @@ REM TESTS_SUITE_S function: run the compiled tests goto END ) ) else if /i not [%ARG:suiteS=%] == [%ARG%] ( - echo Running all test suites. + echo %~nx0: running all test suites. for %%i in (test\test_*.vcxproj) do ( - echo Running test\%BUILD_TYPE%\%%~ni.exe : + echo %~nx0: running test\%BUILD_TYPE%\%%~ni.exe : test\%BUILD_TYPE%\%%~ni.exe if ERRORLEVEL 1 ( goto END @@ -472,7 +473,7 @@ REM TESTS_SUITE_S function: run the compiled tests set crr=%%a if /i ["!crr:~0,6!"] == ["suite:"] ( set SUITE=!crr:~6! - echo Running one suite: !SUITE! + echo %~nx0: running one suite: !SUITE!. test\%BUILD_TYPE%\!SUITE!.exe if ERRORLEVEL 1 ( goto END @@ -486,7 +487,7 @@ REM TESTS_SUITE_S function: run the compiled tests REM INSTALL_DO function: copy DLLs (libcurl, odbc) into the install :INSTALL_DO - echo Installing the driver files. + echo %~nx0: installing the driver files. MSBuild INSTALL.vcxproj !MSBUILD_ARGS! if ERRORLEVEL 1 ( goto END @@ -496,7 +497,7 @@ REM INSTALL_DO function: copy DLLs (libcurl, odbc) into the install REM PACKAGE_DO function: generate deliverable package :PACKAGE_DO - echo Packaging the driver files. + echo %~nx0: packaging the driver files. MSBuild PACKAGE.vcxproj !MSBUILD_ARGS! goto:eof @@ -504,16 +505,16 @@ REM PACKAGE_DO function: generate deliverable package REM REGADD function: add driver into the registry :REGADD - echo Adding driver into the registry. + echo %~nx0: adding driver into the registry. REM check if driver exists, otherwise the filename is unknown if not exist %BUILD_DIR%\%BUILD_TYPE%\%DRIVER_BASE_NAME%*.dll ( - echo Error: Driver can only be added into the registry once built. + echo %~nx0: ERROR: driver can only be added into the registry once built. goto END ) for /f %%i in ("%BUILD_DIR%\%BUILD_TYPE%\%DRIVER_BASE_NAME%*.dll") do set DRVNAME=%%~nxi - echo Adding ESODBC driver %INSTALL_DIR%\!DRVNAME! to the registry. + echo %~nx0: adding ESODBC driver %INSTALL_DIR%\!DRVNAME! to the registry. reg add "HKLM\SOFTWARE\ODBC\ODBCINST.INI\ODBC Drivers" ^ /v "Elasticsearch ODBC" /t REG_SZ /d Installed /f /reg:!BARCH! @@ -532,7 +533,7 @@ REM REGADD function: add driver into the registry REM REGDEL function: remove driver from the registry :REGDEL - echo Removing ESODBC driver from the registry. + echo %~nx0: removing ESODBC driver from the registry. reg delete "HKLM\SOFTWARE\ODBC\ODBCINST.INI\ODBC Drivers" ^ /v "Elasticsearch ODBC" /f /reg:!BARCH! From 1ded820cd7f67a84a53ce65b9ecfa39bf2fa5d02 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Mon, 17 Sep 2018 21:50:52 +0200 Subject: [PATCH 3/7] add modelines to test files - some test files still need migration to project's style --- test/test_conversion_c2sql_boolean.cc | 1 + test/test_conversion_c2sql_numeric.cc | 1 + test/test_conversion_c2sql_timestamp.cc | 1 + test/test_conversion_c2sql_varchar.cc | 1 + test/test_conversion_compatibility.cc | 1 + test/test_sqlgetdata.cc | 1 + 6 files changed, 6 insertions(+) diff --git a/test/test_conversion_c2sql_boolean.cc b/test/test_conversion_c2sql_boolean.cc index 3180ed27..9b74ba45 100644 --- a/test/test_conversion_c2sql_boolean.cc +++ b/test/test_conversion_c2sql_boolean.cc @@ -221,3 +221,4 @@ TEST_F(ConvertC2SQL_Boolean, Binary2Boolean_fail_22003) } // test namespace +/* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : */ diff --git a/test/test_conversion_c2sql_numeric.cc b/test/test_conversion_c2sql_numeric.cc index b52d3508..c5c2e0d6 100644 --- a/test/test_conversion_c2sql_numeric.cc +++ b/test/test_conversion_c2sql_numeric.cc @@ -465,3 +465,4 @@ TEST_F(ConvertC2SQL_Numeric, Numeric2HFloat) } // test namespace +/* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : */ diff --git a/test/test_conversion_c2sql_timestamp.cc b/test/test_conversion_c2sql_timestamp.cc index 52ab0994..8c9b4f9b 100644 --- a/test/test_conversion_c2sql_timestamp.cc +++ b/test/test_conversion_c2sql_timestamp.cc @@ -279,3 +279,4 @@ TEST_F(ConvertC2SQL_Timestamp, Time2Timestamp_unimplemented_HYC00) } // test namespace +/* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : */ diff --git a/test/test_conversion_c2sql_varchar.cc b/test/test_conversion_c2sql_varchar.cc index e8a08afb..0eb6eec5 100644 --- a/test/test_conversion_c2sql_varchar.cc +++ b/test/test_conversion_c2sql_varchar.cc @@ -284,3 +284,4 @@ TEST_F(ConvertC2SQL_Varchar, Double2Varchar) } // test namespace +/* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : */ diff --git a/test/test_conversion_compatibility.cc b/test/test_conversion_compatibility.cc index d13526b3..295e32ab 100644 --- a/test/test_conversion_compatibility.cc +++ b/test/test_conversion_compatibility.cc @@ -239,3 +239,4 @@ TEST_F(ConversionCompatibility, ConvCompat) } // test namespace +/* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : */ diff --git a/test/test_sqlgetdata.cc b/test/test_sqlgetdata.cc index bd4eaaa5..7860d376 100644 --- a/test/test_sqlgetdata.cc +++ b/test/test_sqlgetdata.cc @@ -641,3 +641,4 @@ TEST_F(GetData, ScaledFloat2Char_whole) { } // test namespace +/* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : */ From d870a21417fce7dc291b74a235d6379a8cc66b3d Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Mon, 17 Sep 2018 22:04:52 +0200 Subject: [PATCH 4/7] add the SSL configuration for libcurl - configure the needed params for libcurl to be able to connect to a SSL-enabled Elasticsearch. This includes settings for: * checking the CA, hostname, revokation info; these params corespond to a "secure" connection string integer parameter. * reading a public CA file (if server's cert is not part of PKI). - register a debug call back to libcurl; - refactor part of HTTP request/response handling, covering also the initial connection test; - add a few "offline" unit test for a small part of the refactored code (the error handling). --- driver/connect.c | 394 +++++++++++++++++++++++++++++-------------- driver/convert.c | 8 +- driver/defs.h | 4 +- driver/dsn.c | 3 + driver/dsn.h | 4 +- driver/error.c | 18 +- driver/error.h | 2 + driver/handles.h | 11 ++ driver/queries.c | 76 +++++---- driver/queries.h | 2 +- driver/util.c | 37 +++- driver/util.h | 10 +- test/test_queries.cc | 95 +++++++++++ test/test_util.cc | 26 +++ 14 files changed, 513 insertions(+), 177 deletions(-) create mode 100644 test/test_queries.cc diff --git a/driver/connect.c b/driver/connect.c index ed327536..227c11cd 100644 --- a/driver/connect.c +++ b/driver/connect.c @@ -96,9 +96,6 @@ typedef struct { * HTTP headers used for all requests (Content-Type, Accept). */ static struct curl_slist *http_headers = NULL; -#ifndef NDEBUG -static char curl_err_buff[CURL_ERROR_SIZE]; -#endif /* NDEBUG */ BOOL connect_init() { @@ -134,6 +131,37 @@ void connect_cleanup() curl_global_cleanup(); } +#ifndef NDEBUG +static int debug_callback(CURL *handle, curl_infotype type, char *data, + size_t size, void *userptr) +{ + esodbc_dbc_st *dbc = DBCH(userptr); + char *info_type; + char *info_types[] = {"text", "header-in", "header-out", "data-in", + "data-out" + }; + + switch (type) { + case CURLINFO_TEXT: + if (size) { + /* strip trailing \n */ + size --; + } + case CURLINFO_HEADER_IN: + case CURLINFO_HEADER_OUT: + case CURLINFO_DATA_IN: + case CURLINFO_DATA_OUT: + info_type = info_types[type - CURLINFO_TEXT]; + break; + case CURLINFO_SSL_DATA_IN: + case CURLINFO_SSL_DATA_OUT: + return 0; + } + DBGH(dbc, "libcurl: %s: [%zu] `%.*s`.", info_type, size, size, data); + return 0; +} +#endif /* NDEBUG */ + /* * "ptr points to the delivered data, and the size of that data is size * multiplied with nmemb." @@ -202,7 +230,7 @@ static size_t write_callback(char *ptr, size_t size, size_t nmemb, return have; } -static SQLRETURN init_curl(esodbc_dbc_st *dbc) +static SQLRETURN dbc_curl_init(esodbc_dbc_st *dbc) { CURLcode res; CURL *curl; @@ -216,74 +244,113 @@ static SQLRETURN init_curl(esodbc_dbc_st *dbc) RET_HDIAG(dbc, SQL_STATE_HY000, "failed to init the transport", 0); } -#ifndef NDEBUG - res = curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_err_buff); + res = curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, dbc->curl_err_buff); + dbc->curl_err_buff[0] = '\0'; if (res != CURLE_OK) { - ERRH(dbc, "libcurl: failed to set errorbuffer: %s (%d).", - curl_easy_strerror(res), res); - /* not fatal */ + ERRH(dbc, "libcurl: failed to set error buffer."); + goto err; } -#endif /* NDEBUG */ /* set URL to connect to */ res = curl_easy_setopt(curl, CURLOPT_URL, dbc->url.str); if (res != CURLE_OK) { - ERRH(dbc, "libcurl: failed to set URL `%s`: %s (%d).", dbc->url.str, - curl_easy_strerror(res), res); + ERRH(dbc, "libcurl: failed to set URL `%s`.", dbc->url.str); goto err; } /* always do POSTs (seconded by CURLOPT_POSTFIELDS) */ res = curl_easy_setopt(curl, CURLOPT_POST, 1L); if (res != CURLE_OK) { - ERRH(dbc, "libcurl: failed to set POST method: %s (%d).", - curl_easy_strerror(res), res); + ERRH(dbc, "libcurl: failed to set method to POST."); goto err; } /* set the Content-Type, Accept HTTP headers */ res = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, http_headers); if (res != CURLE_OK) { - ERRH(dbc, "libcurl: failed to set headers: %s (%d).", - curl_easy_strerror(res), res); + ERRH(dbc, "libcurl: failed to set HTTP headers list."); goto err; } /* set the behavior for redirection */ res = curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, dbc->follow); if (res != CURLE_OK) { - ERRH(dbc, "libcurl: failed to set redirection: %s (%d).", - curl_easy_strerror(res), res); + ERRH(dbc, "libcurl: failed to set redirection behavior."); goto err; } + if (dbc->secure) { + res = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, + ESODBC_SEC_CHECK_CA <= dbc->secure ? 1L : 0L); + if (res != CURLE_OK) { + ERRH(dbc, "libcurl: failed to enable CA check."); + goto err; + } + if (ESODBC_SEC_CHECK_CA <= dbc->secure) { + /* set path to CA */ + if (dbc->ca_path.cnt) { + res = curl_easy_setopt(curl, CURLOPT_CAINFO, dbc->ca_path.str); + if (res != CURLE_OK) { + ERRH(dbc, "libcurl: failed to set CA path."); + goto err; + } + } + /* verify host name */ + res = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, + ESODBC_SEC_CHECK_HOST <= dbc->secure ? 2L : 0L); + if (res != CURLE_OK) { + ERRH(dbc, "libcurl: failed to enable host check."); + goto err; + } + /* verify the revokation chain? */ + res = curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, + ESODBC_SEC_CHECK_REVOKE <= dbc->secure ? + 0L : CURLSSLOPT_NO_REVOKE); + if (res != CURLE_OK) { + ERRH(dbc, "libcurl: failed to enable host check."); + goto err; + } + } + } + + /* set authentication parameters */ if (dbc->uid.cnt) { /* set the authentication methods: * "basic" is currently - 7.0.0 - the only supported method */ - res = curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY); + res = curl_easy_setopt(curl, CURLOPT_HTTPAUTH, + /* libcurl (7.61.0) won't pick Basic auth over SSL when + * _ANY is used. -- ??? XXX */ + dbc->secure ? CURLAUTH_BASIC : CURLAUTH_ANY); if (res != CURLE_OK) { - ERRH(dbc, "libcurl: failed to set HTTP auth methods: %s (%d).", - curl_easy_strerror(res), res); + ERRH(dbc, "libcurl: failed to set HTTP auth methods."); goto err; } /* set the username */ res = curl_easy_setopt(curl, CURLOPT_USERNAME, dbc->uid.str); if (res != CURLE_OK) { - ERRH(dbc, "libcurl: failed to set auth username: %s (%d).", - curl_easy_strerror(res), res); + ERRH(dbc, "libcurl: failed to set auth username."); goto err; } /* set the password */ if (dbc->pwd.cnt) { res = curl_easy_setopt(curl, CURLOPT_PASSWORD, dbc->pwd.str); if (res != CURLE_OK) { - ERRH(dbc, "libcurl: failed to set auth password: %s (%d).", - curl_easy_strerror(res), res); + ERRH(dbc, "libcurl: failed to set auth password."); goto err; } } else { INFOH(dbc, "no password for username `" LCPDL "`.", LCSTR(&dbc->uid)); } + if (dbc->follow) { + /* restrict sharing credentials to first contacted host? */ + res = curl_easy_setopt(curl, CURLOPT_UNRESTRICTED_AUTH, + /* if not secure, "make it work" */ + dbc->secure ? 0L : 1L); + if (res != CURLE_OK) { + ERRH(dbc, "libcurl: failed to set unrestricted auth."); + goto err; + } + } } else { INFOH(dbc, "no username provided: auth disabled."); } @@ -291,26 +358,51 @@ static SQLRETURN init_curl(esodbc_dbc_st *dbc) /* set the write call-back for answers */ res = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); if (res != CURLE_OK) { - ERRH(dbc, "libcurl: failed to set write callback: %s (%d).", - curl_easy_strerror(res), res); + ERRH(dbc, "libcurl: failed to set write callback."); goto err; } /* ... and its argument */ res = curl_easy_setopt(curl, CURLOPT_WRITEDATA, dbc); if (res != CURLE_OK) { - ERRH(dbc, "libcurl: failed to set callback argument: %s (%d).", - curl_easy_strerror(res), res); + ERRH(dbc, "libcurl: failed to set callback argument."); goto err; } +#ifndef NDEBUG + if (LOG_LEVEL_DBG <= _esodbc_log_level) { + res = curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, debug_callback); + if (res != CURLE_OK) { + ERRH(dbc, "libcurl: failed to set debug callback."); + goto err; + } + /* ... and its argument */ + res = curl_easy_setopt(curl, CURLOPT_DEBUGDATA, dbc); + if (res != CURLE_OK) { + ERRH(dbc, "libcurl: failed to set dbg callback argument."); + goto err; + } + res = curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + if (res != CURLE_OK) { + ERRH(dbc, "libcurl: failed to activate verbose mode."); + goto err; + } + } +#endif /* NDEBUG */ + dbc->curl = curl; DBGH(dbc, "libcurl: new handle 0x%p.", curl); return SQL_SUCCESS; err: + assert(res != CURLE_OK); + ERRH(dbc, "libcurl: failure code %d, message: %s.", res, + curl_easy_strerror(res)); assert(curl); curl_easy_cleanup(curl); - RET_HDIAG(dbc, SQL_STATE_HY000, "failed to init the transport", 0); + return post_c_diagnostic(&dbc->hdr.diag, SQL_STATE_HY000, + /* propagate CURL's message, if there's any available */ + dbc->curl_err_buff[0] ? dbc->curl_err_buff : + "failed to init the transport", res); } static void cleanup_curl(esodbc_dbc_st *dbc) @@ -319,18 +411,19 @@ static void cleanup_curl(esodbc_dbc_st *dbc) return; } DBGH(dbc, "libcurl: handle 0x%p cleanup.", dbc->curl); - curl_easy_cleanup(dbc->curl); /* TODO: _reset() rather? */ + dbc->curl_err = CURLE_OK; + dbc->curl_err_buff[0] = '\0'; + + curl_easy_cleanup(dbc->curl); dbc->curl = NULL; } -static CURLcode dbc_execute(esodbc_dbc_st *dbc, long *code, cstr_st *resp) +static BOOL dbc_curl_perform(esodbc_dbc_st *dbc, long *code, cstr_st *resp) { - CURLcode res; - assert(dbc->abuff == NULL); /* execute the request */ - res = curl_easy_perform(dbc->curl); + dbc->curl_err = curl_easy_perform(dbc->curl); /* copy answer references */ resp->str = dbc->abuff; @@ -341,47 +434,66 @@ static CURLcode dbc_execute(esodbc_dbc_st *dbc, long *code, cstr_st *resp) dbc->apos = 0; dbc->alen = 0; - if (res != CURLE_OK) { - return res; + if (dbc->curl_err != CURLE_OK) { + ERRH(dbc, "libcurl: failed to perform."); + goto err; } - res = curl_easy_getinfo(dbc->curl, CURLINFO_RESPONSE_CODE, code); - if (res != CURLE_OK) { - return res; + dbc->curl_err = curl_easy_getinfo(dbc->curl, CURLINFO_RESPONSE_CODE, code); + if (dbc->curl_err != CURLE_OK) { + ERRH(dbc, "libcurl: failed to retrieve response code."); + goto err; } DBGH(dbc, "libcurl: request answered, received code %ld and %zd bytes" " back.", *code, resp->cnt); - return CURLE_OK; + return TRUE; + +err: + ERRH(dbc, "libcurl: failure code %d, message: %s.", dbc->curl_err, + curl_easy_strerror(dbc->curl_err)); + return FALSE; } -static CURLcode dbc_prepare_post(esodbc_dbc_st *dbc, SQLULEN tout, +static BOOL dbc_curl_prepare(esodbc_dbc_st *dbc, SQLULEN tout, const cstr_st *u8body) { - CURLcode res; + assert(dbc->curl); + + dbc->curl_err = CURLE_OK; + dbc->curl_err_buff[0] = '\0'; if (0 < tout) { - DBGH(dbc, "libcurl: setting timeout: %ld.", tout); - res = curl_easy_setopt(dbc->curl, CURLOPT_TIMEOUT, tout); - if (res != CURLE_OK) { - return res; + dbc->curl_err = curl_easy_setopt(dbc->curl, CURLOPT_TIMEOUT, tout); + if (dbc->curl_err != CURLE_OK) { + ERRH(dbc, "libcurl: failed to set timeout=%ld: %s (%d).", tout, + curl_easy_strerror(dbc->curl_err), dbc->curl_err); + return FALSE; } } /* len of the body */ - DBGH(dbc, "libcurl: setting post fieldsize: %ld.", u8body->cnt); - res = curl_easy_setopt(dbc->curl, CURLOPT_POSTFIELDSIZE, u8body->cnt); - if (res != CURLE_OK) { - return res; + dbc->curl_err = curl_easy_setopt(dbc->curl, CURLOPT_POSTFIELDSIZE_LARGE, + u8body->cnt); + if (dbc->curl_err != CURLE_OK) { + ERRH(dbc, "libcurl: failed to set post fieldsize: %zu.", u8body->cnt); + goto err; } /* body itself */ - DBGH(dbc, "libcurl: setting post fields to `" LCPDL "`.", LCSTR(u8body)); - res = curl_easy_setopt(dbc->curl, CURLOPT_POSTFIELDS, u8body->str); - if (res != CURLE_OK) { - return res; + dbc->curl_err = curl_easy_setopt(dbc->curl, CURLOPT_POSTFIELDS, + u8body->str); + if (dbc->curl_err != CURLE_OK) { + ERRH(dbc, "libcurl: failed to set post fields: `" LCPDL "`.", + LCSTR(u8body)); + goto err; } - return CURLE_OK; + return TRUE; + +err: + ERRH(dbc, "libcurl: failure code %d, message: %s.", dbc->curl_err, + curl_easy_strerror(dbc->curl_err)); + return FALSE; } /* @@ -389,8 +501,7 @@ static CURLcode dbc_prepare_post(esodbc_dbc_st *dbc, SQLULEN tout, */ SQLRETURN post_json(esodbc_stmt_st *stmt, const cstr_st *u8body) { - // char *const answer, /* buffer to receive the answer in */ - // long avail /*size of the answer buffer */) + SQLRETURN ret; CURLcode res = CURLE_OK; esodbc_dbc_st *dbc = stmt->hdr.dbc; SQLULEN tout; @@ -404,92 +515,84 @@ SQLRETURN post_json(esodbc_stmt_st *stmt, const cstr_st *u8body) ESODBC_MUX_LOCK(&dbc->curl_mux); if (! dbc->curl) { - init_curl(dbc); + ret = dbc_curl_init(dbc); + if (! SQL_SUCCEEDED(ret)) { + stmt->hdr.diag = dbc->hdr.diag; + return ret; + } } /* set timeout as maximum between connection and statement value */ tout = dbc->timeout < stmt->query_timeout ? stmt->query_timeout : dbc->timeout; - res = dbc_prepare_post(dbc, tout, u8body); - if (res != CURLE_OK) { - goto err; - } - - res = dbc_execute(dbc, &code, &resp); - if (res != CURLE_OK) { - goto err; - } - - /* expect a 200 with body; everything else is failure */ - if (code == 200 && resp.cnt) { - ESODBC_MUX_UNLOCK(&dbc->curl_mux); - - assert(resp.str); - return attach_answer(stmt, resp.str, resp.cnt); - } else { - cleanup_curl(dbc); - ESODBC_MUX_UNLOCK(&dbc->curl_mux); - - ERRH(stmt, "libcurl: answer code: %ld, answer lenght: %zd " - "(non 200 HTTP response with body).", code, resp.cnt); - if (resp.cnt) { - assert(resp.str); - return attach_error(stmt, resp.str, resp.cnt); + code = -1; /* init value */ + if (dbc_curl_prepare(dbc, tout, u8body) && + dbc_curl_perform(dbc, &code, &resp)) { + if (code == 200) { + if (resp.cnt) { + ESODBC_MUX_UNLOCK(&dbc->curl_mux); + return attach_answer(stmt, resp.str, resp.cnt); + } else { + ERRH(stmt, "empty body received with 200 response code."); + } } - goto err_net; /* skip unlocking */ + } else { + assert (dbc->curl_err != CURLE_OK); + post_c_diagnostic(&stmt->hdr.diag, SQL_STATE_HY000, dbc->curl_err_buff, + dbc->curl_err); + code = -1; /* make sure that curl's error will surface */ } - -err: - ERRH(stmt, "libcurl: request failed (timeout:%llu, body:`" LCPDL "`) " - "failed: '%s' (%d).", tout, LCSTR(u8body), - res != CURLE_OK ? curl_easy_strerror(res) : "", res); + /* something went wrong */ cleanup_curl(dbc); ESODBC_MUX_UNLOCK(&dbc->curl_mux); -err_net: /* error occured after network xfer; lock is freed */ + /* was there an answer received correctly ? */ + if (0 < code) { + attach_error(stmt, &resp, code); + } + + /* an answer might have been received, but a late curl error (like + * fetching the result code) could have occurred. */ if (resp.str) { free(resp.str); resp.str = NULL; - /* if buffer had been set, the error occured at/after _perform() */ - RET_HDIAG(stmt, SQL_STATE_08S01, "data transfer failure", res); } - RET_HDIAG(stmt, SQL_STATE_HY000, "failed to init transport", res); + RET_STATE(stmt->hdr.diag.state); } static SQLRETURN test_connect(esodbc_dbc_st *dbc) { - CURLcode res; - long code = -1; + long code; cstr_st u8body = MK_CSTR(HTTP_TEST_JSON); cstr_st resp = (cstr_st) { NULL, 0 }; - res = dbc_prepare_post(dbc, dbc->timeout, &u8body); - if (res != CURLE_OK) { - goto err; + if (! (dbc_curl_prepare(dbc, dbc->timeout, &u8body) && + dbc_curl_perform(dbc, &code, &resp))) { + post_c_diagnostic(&dbc->hdr.diag, SQL_STATE_HY000, dbc->curl_err_buff, + dbc->curl_err); + cleanup_curl(dbc); + code = -1; /* make sure that curl's error will surface */ } - res = dbc_execute(dbc, &code, &resp); - if (res != CURLE_OK || code != 200) { - goto err; + if (code == 200) { + DBGH(dbc, "test connection succesful."); + dbc->hdr.diag.state = SQL_STATE_00000; + } else if (0 < code) { + attach_error(dbc, &resp, code); + } else { + /* libcurl failure */ + assert(dbc->hdr.diag.state != SQL_STATE_00000); } - DBGH(dbc, "test connection succesful."); - return SQL_SUCCESS; - -err: - ERRH(dbc, "libcurl: test connection failed: %s (%d).", - curl_easy_strerror(res), res); - if (0 < code) { - ERRH(dbc, "libcurl: test connection code: %ld.", code); - } if (resp.cnt) { - ERRH(dbc, "libcurl: test connection answer: `" LCPDL "`.", - LCSTR(&resp)); + free(resp.str); + resp.str = NULL; } - return SQL_ERROR; + + RET_STATE(dbc->hdr.diag.state); } /* @@ -499,18 +602,39 @@ static SQLRETURN process_config(esodbc_dbc_st *dbc, esodbc_dsn_attrs_st *attrs) { esodbc_state_et state = SQL_STATE_HY000; int cnt; - BOOL secure; + SQLBIGINT secure; long long timeout, max_body_size, max_fetch_size; SQLWCHAR buff_url[ESODBC_MAX_URL_LEN]; wstr_st url = (wstr_st) { buff_url, /*will be init'ed later*/0 }; + if (! str2bigint(&attrs->secure, /*wide?*/TRUE, &secure)) { + ERRH(dbc, "failed to read secure param `" LWPDL "`.", + LWSTR(&attrs->secure)); + goto err; + } + if (secure < ESODBC_SEC_NONE || ESODBC_SEC_MAX <= secure) { + ERRH(dbc, "invalid secure param `" LWPDL "` (not within %d - %d).", + LWSTR(&attrs->secure), ESODBC_SEC_NONE, ESODBC_SEC_CHECK_HOST); + goto err; + } else { + dbc->secure = (long)secure; + INFOH(dbc, "connection security level: %ld.", dbc->secure); + } + + if (secure) { + if (! wstr_to_utf8(&attrs->ca_path, &dbc->ca_path)) { + ERRNH(dbc, "failed to convert CA path `" LWPDL "` to UTF8.", + LWSTR(&attrs->ca_path)); + goto err; + } + INFOH(dbc, "CA path: `%s`.", dbc->ca_path.str); + } + /* * URL of the cluster */ - secure = wstr2bool(&attrs->secure); - INFOH(dbc, "connect secure: %s.", secure ? "true" : "false"); cnt = swprintf(url.str, sizeof(buff_url)/sizeof(*buff_url), L"http" WPFCP_DESC "://" WPFWP_LDESC ":" WPFWP_LDESC ELASTIC_SQL_PATH, secure ? "s" : "", LWSTR(&attrs->server), @@ -609,7 +733,7 @@ static SQLRETURN process_config(esodbc_dbc_st *dbc, esodbc_dsn_attrs_st *attrs) RET_HDIAGS(dbc, SQL_STATE_HY001); } dbc->fetch.str[dbc->fetch.slen] = 0; - ansi_w2c(attrs->max_fetch_size.str, dbc->fetch.str, dbc->fetch.slen); + ascii_w2c(attrs->max_fetch_size.str, dbc->fetch.str, dbc->fetch.slen); } INFOH(dbc, "fetch_size: %s.", dbc->fetch.str ? dbc->fetch.str : "none" ); @@ -641,25 +765,40 @@ static SQLRETURN process_config(esodbc_dbc_st *dbc, esodbc_dsn_attrs_st *attrs) /* release all resources, except the handler itself */ void cleanup_dbc(esodbc_dbc_st *dbc) { + if (dbc->ca_path.str) { + free(dbc->ca_path.str); + dbc->ca_path.str = NULL; + dbc->ca_path.cnt = 0; + } else { + assert(dbc->ca_path.cnt == 0); + } if (dbc->url.str) { free(dbc->url.str); dbc->url.str = NULL; dbc->url.cnt = 0; + } else { + assert(dbc->url.cnt == 0); } if (dbc->uid.str) { free(dbc->uid.str); dbc->uid.str = NULL; dbc->uid.cnt = 0; + } else { + assert(dbc->uid.cnt == 0); } if (dbc->pwd.str) { free(dbc->pwd.str); dbc->pwd.str = NULL; dbc->pwd.cnt = 0; + } else { + assert(dbc->pwd.cnt == 0); } if (dbc->fetch.str) { free(dbc->fetch.str); dbc->fetch.str = NULL; dbc->fetch.slen = 0; + } else { + assert(dbc->fetch.slen == 0); } if (dbc->dsn.str) { free(dbc->dsn.str); @@ -686,18 +825,18 @@ void cleanup_dbc(esodbc_dbc_st *dbc) static SQLRETURN do_connect(esodbc_dbc_st *dbc, esodbc_dsn_attrs_st *attrs) { SQLRETURN ret; - char *url = NULL; /* multiple connection attempts are possible (when prompting user) */ cleanup_dbc(dbc); + /* set the DBC params based on given attributes (but no validation atp) */ ret = process_config(dbc, attrs); if (! SQL_SUCCEEDED(ret)) { return ret; } /* init libcurl objects */ - ret = init_curl(dbc); + ret = dbc_curl_init(dbc); if (! SQL_SUCCEEDED(ret)) { ERRH(dbc, "failed to init transport."); return ret; @@ -706,20 +845,13 @@ static SQLRETURN do_connect(esodbc_dbc_st *dbc, esodbc_dsn_attrs_st *attrs) /* perform a connection test, to fail quickly if wrong params AND * populate the DNS cache */ ret = test_connect(dbc); - /* still ok if fails */ - if (curl_easy_getinfo(dbc->curl, CURLINFO_EFFECTIVE_URL, &url) != - CURLE_OK) { - url = ""; - } if (! SQL_SUCCEEDED(ret)) { - ERRH(dbc, "test connection to URL `%s` failed!", url); - cleanup_curl(dbc); - RET_HDIAGS(dbc, SQL_STATE_08001); + ERRH(dbc, "test connection to URL `%s` failed!", dbc->url.str); } else { - DBGH(dbc, "test connection to URL %s OK.", url); + DBGH(dbc, "test connection to URL %s OK.", dbc->url.str); } - return SQL_SUCCESS; + return ret; } @@ -1103,7 +1235,7 @@ static void *copy_types_rows(estype_row_st *type_row, SQLULEN rows_fetched, /* convert type_name to C and copy it too */ types[i].type_name_c.str = (char *)pos; /**/ pos += types[i].type_name.cnt + /*\0*/1; - if ((c = ansi_w2c(types[i].type_name.str, types[i].type_name_c.str, + if ((c = ascii_w2c(types[i].type_name.str, types[i].type_name_c.str, types[i].type_name.cnt)) < 0) { ERR("failed to convert ES/SQL type `" LWPDL "` to C-str.", LWSTR(&types[i].type_name)); diff --git a/driver/convert.c b/driver/convert.c index e07acb60..a3d4da02 100644 --- a/driver/convert.c +++ b/driver/convert.c @@ -1362,7 +1362,7 @@ static SQLRETURN wstr_to_wstr(esodbc_rec_st *arec, esodbc_rec_st *irec, static BOOL xstr_to_timestamp_struct(xstr_st *xstr, TIMESTAMP_STRUCT *tss, cstr_st *ts_buff) { - /* need the 0-term in the buff, since ansi_w2c will write it */ + /* need the 0-term in the buff, since ascii_w2c will write it */ char buff[sizeof(ESODBC_ISO8601_TEMPLATE)/*+\0*/]; cstr_st ts_str, *ts_ptr; timestamp_t tsp; @@ -1385,7 +1385,7 @@ static BOOL xstr_to_timestamp_struct(xstr_st *xstr, TIMESTAMP_STRUCT *tss, } /* convert the W-string to C-string; also, copy it directly into out * ts_buff, if given (thus saving one extra copying) */ - ts_ptr->cnt = ansi_w2c(xstr->w.str, ts_ptr->str, xstr->w.cnt) - 1; + ts_ptr->cnt = ascii_w2c(xstr->w.str, ts_ptr->str, xstr->w.cnt) - 1; } else { DBG("converting ISO 8601 `" LCPDL "` to timestamp.", LCSTR(&xstr->c)); if (sizeof(ESODBC_ISO8601_TEMPLATE) - 1 < xstr->c.cnt) { @@ -1445,7 +1445,7 @@ static BOOL parse_timedate(xstr_st *xstr, TIMESTAMP_STRUCT *tss, /* W-strings will eventually require convertion to C-string for TS * conversion => do it now to simplify string analysis */ if (xstr->wide) { - td.cnt = ansi_w2c(xstr->w.str, w2c, xstr->w.cnt) - 1; + td.cnt = ascii_w2c(xstr->w.str, w2c, xstr->w.cnt) - 1; td.str = w2c; } else { td = xstr->c; @@ -2233,7 +2233,7 @@ static SQLRETURN string_to_number(esodbc_rec_st *arec, esodbc_rec_st *irec, /* copy values from app's buffer directly */ if (wide) { /* need a conversion to ANSI */ *len = xstr.w.cnt; - ret = ansi_w2c((SQLWCHAR *)data_ptr, dest, *len); + ret = ascii_w2c((SQLWCHAR *)data_ptr, dest, *len); assert(0 < ret); /* it converted to a float already */ } else { *len = xstr.c.cnt; diff --git a/driver/defs.h b/driver/defs.h index ee3e3620..ea2144be 100644 --- a/driver/defs.h +++ b/driver/defs.h @@ -136,8 +136,8 @@ #define ESODBC_DEF_SERVER "127.0.0.1" /* Elasticsearch'es default port */ #define ESODBC_DEF_PORT "9200" -/* default security (TLS) setting */ -#define ESODBC_DEF_SECURE "no" +/* default security setting: use SSL */ +#define ESODBC_DEF_SECURE "1" /* default global request timeout (0: no timeout) */ #define ESODBC_DEF_TIMEOUT "0" /* don't follow redirection from the server */ diff --git a/driver/dsn.c b/driver/dsn.c index 403f7503..dfbb83fc 100644 --- a/driver/dsn.c +++ b/driver/dsn.c @@ -58,6 +58,7 @@ int assign_dsn_attr(esodbc_dsn_attrs_st *attrs, {&MK_WSTR(ESODBC_DSN_SERVER), &attrs->server}, {&MK_WSTR(ESODBC_DSN_PORT), &attrs->port}, {&MK_WSTR(ESODBC_DSN_SECURE), &attrs->secure}, + {&MK_WSTR(ESODBC_DSN_CA_PATH), &attrs->ca_path}, {&MK_WSTR(ESODBC_DSN_TIMEOUT), &attrs->timeout}, {&MK_WSTR(ESODBC_DSN_FOLLOW), &attrs->follow}, {&MK_WSTR(ESODBC_DSN_CATALOG), &attrs->catalog}, @@ -478,6 +479,7 @@ BOOL write_system_dsn(esodbc_dsn_attrs_st *attrs, BOOL create_new) {&MK_WSTR(ESODBC_DSN_SERVER), &attrs->server}, {&MK_WSTR(ESODBC_DSN_PORT), &attrs->port}, {&MK_WSTR(ESODBC_DSN_SECURE), &attrs->secure}, + {&MK_WSTR(ESODBC_DSN_CA_PATH), &attrs->ca_path}, {&MK_WSTR(ESODBC_DSN_TIMEOUT), &attrs->timeout}, {&MK_WSTR(ESODBC_DSN_FOLLOW), &attrs->follow}, {&MK_WSTR(ESODBC_DSN_CATALOG), &attrs->catalog}, @@ -560,6 +562,7 @@ BOOL write_connection_string(esodbc_dsn_attrs_st *attrs, {&attrs->server, ESODBC_DSN_SERVER}, {&attrs->port, ESODBC_DSN_PORT}, {&attrs->secure, ESODBC_DSN_SECURE}, + {&attrs->ca_path, ESODBC_DSN_CA_PATH}, {&attrs->timeout, ESODBC_DSN_TIMEOUT}, {&attrs->follow, ESODBC_DSN_FOLLOW}, {&attrs->catalog, ESODBC_DSN_CATALOG}, diff --git a/driver/dsn.h b/driver/dsn.h index d7b0fe56..f7da7617 100644 --- a/driver/dsn.h +++ b/driver/dsn.h @@ -24,6 +24,7 @@ #define ESODBC_DSN_SERVER "Server" #define ESODBC_DSN_PORT "Port" #define ESODBC_DSN_SECURE "Secure" +#define ESODBC_DSN_CA_PATH "CAPath" #define ESODBC_DSN_TIMEOUT "Timeout" #define ESODBC_DSN_FOLLOW "Follow" #define ESODBC_DSN_CATALOG "Catalog" @@ -45,6 +46,7 @@ typedef struct { wstr_st server; wstr_st port; wstr_st secure; + wstr_st ca_path; wstr_st timeout; wstr_st follow; wstr_st catalog; @@ -53,7 +55,7 @@ typedef struct { wstr_st max_body_size; wstr_st trace_file; wstr_st trace_level; -#define ESODBC_DSN_ATTRS_COUNT 18 +#define ESODBC_DSN_ATTRS_COUNT 19 SQLWCHAR buff[ESODBC_DSN_ATTRS_COUNT * ESODBC_DSN_MAX_ATTR_LEN]; } esodbc_dsn_attrs_st; diff --git a/driver/error.c b/driver/error.c index ec18dcde..0e1f3834 100644 --- a/driver/error.c +++ b/driver/error.c @@ -39,7 +39,7 @@ SQLRETURN post_diagnostic(esodbc_diag_st *dest, esodbc_state_et state, if (ebufsz <= pos + tcnt) { wcsncpy(dest->text + pos, text, ebufsz - (pos + 1)); - dest->text[ebufsz - 1] = 0; + dest->text[ebufsz - 1] = L'\0'; assert(1 < ebufsz && ebufsz < USHRT_MAX); dest->text_len = (SQLUSMALLINT)ebufsz - 1; } else { @@ -50,9 +50,25 @@ SQLRETURN post_diagnostic(esodbc_diag_st *dest, esodbc_state_et state, dest->text, dest->text_len, dest->native_code); RET_STATE(state); +} +SQLRETURN post_c_diagnostic(esodbc_diag_st *dest, esodbc_state_et state, + SQLCHAR *text, SQLINTEGER code) +{ + SQLWCHAR wtext[sizeof(dest->text)/sizeof(*dest->text)], *ptr; + if (text) { + if (ascii_c2w(text, wtext, sizeof(wtext)/sizeof(*wtext)) < 0) { + ERR("failed to convert diagnostic message `%s`.", text); + wtext[0] = L'\0'; + } + ptr = wtext; + } else { + ptr = NULL; + } + return post_diagnostic(dest, state, ptr, code); } + SQLRETURN post_row_diagnostic(esodbc_diag_st *dest, esodbc_state_et state, SQLWCHAR *text, SQLINTEGER code, SQLLEN nrow, SQLINTEGER ncol) { diff --git a/driver/error.h b/driver/error.h index 1ecf02f1..71d0f5d2 100644 --- a/driver/error.h +++ b/driver/error.h @@ -445,6 +445,8 @@ typedef struct { void init_diagnostic(esodbc_diag_st *dest); SQLRETURN post_diagnostic(esodbc_diag_st *dest, esodbc_state_et state, SQLWCHAR *text, SQLINTEGER code); +SQLRETURN post_c_diagnostic(esodbc_diag_st *dest, esodbc_state_et state, + SQLCHAR *text, SQLINTEGER code); /* post state into the diagnostic and return state's return code */ #define RET_DIAG(_d/*est*/, _s/*tate*/, _t/*ext*/, _c/*ode*/) \ return post_diagnostic(_d, _s, _t, _c) diff --git a/driver/handles.h b/driver/handles.h index 8c01f56d..c57233c3 100644 --- a/driver/handles.h +++ b/driver/handles.h @@ -128,6 +128,15 @@ typedef struct struct_dbc { wstr_st dsn; /* data source name SQLGetInfo(SQL_DATA_SOURCE_NAME) */ wstr_st server; /* ~ name; requested with SQLGetInfo(SQL_SERVER_NAME) */ cstr_st url; + enum { + ESODBC_SEC_NONE = 0, + ESODBC_SEC_USE_SSL, + ESODBC_SEC_CHECK_CA, + ESODBC_SEC_CHECK_HOST, + ESODBC_SEC_CHECK_REVOKE, + ESODBC_SEC_MAX /* meta */ + } secure; + cstr_st ca_path; cstr_st uid; cstr_st pwd; SQLUINTEGER timeout; @@ -146,6 +155,8 @@ typedef struct struct_dbc { SQLINTEGER max_varchar_size; CURL *curl; /* cURL handle */ + CURLcode curl_err; + char curl_err_buff[CURL_ERROR_SIZE]; char *abuff; /* buffer holding the answer */ size_t alen; /* size of abuff */ size_t apos; /* current write position in the abuff */ diff --git a/driver/queries.c b/driver/queries.c index 15b193d9..d2fc84ee 100644 --- a/driver/queries.c +++ b/driver/queries.c @@ -310,19 +310,17 @@ SQLRETURN TEST_API attach_answer(esodbc_stmt_st *stmt, char *buff, size_t blen) return SQL_SUCCESS; } -/* - * Parse an error and push it as statement diagnostic. - */ -SQLRETURN TEST_API attach_error(esodbc_stmt_st *stmt, char *buff, size_t blen) +/* parse the error as SQL pluggin generated error */ +static BOOL attach_sql_error(SQLHANDLE hnd, cstr_st *body) { + BOOL ret; UJObject obj, o_status, o_error, o_type, o_reason; const wchar_t *wtype, *wreason; size_t tlen, rlen, left; - wchar_t wbuf[sizeof(((esodbc_diag_st *)NULL)->text) / - sizeof(*((esodbc_diag_st *)NULL)->text)]; - size_t wbuflen = sizeof(wbuf)/sizeof(*wbuf); + wchar_t wbuf[SQL_MAX_MESSAGE_LENGTH]; + size_t wbuflen; int n; - void *state = NULL; + void *state; const wchar_t *outer_keys[] = { MK_WPTR(JSON_ANSWER_ERROR), MK_WPTR(JSON_ANSWER_STATUS) @@ -332,28 +330,24 @@ SQLRETURN TEST_API attach_error(esodbc_stmt_st *stmt, char *buff, size_t blen) MK_WPTR(JSON_ANSWER_ERR_REASON) }; - INFOH(stmt, "REST request failed with `%.*s` (%zd).", blen, buff, blen); + ret = FALSE; + state = NULL; /* parse the entire JSON answer */ - obj = UJDecode(buff, blen, NULL, &state); + obj = UJDecode(body->str, body->cnt, NULL, &state); if (! obj) { - ERRH(stmt, "failed to decode JSON answer (`%.*s`): %s.", - blen, buff, state ? UJGetError(state) : ""); - SET_HDIAG(stmt, SQL_STATE_HY000, MSG_INV_SRV_ANS, 0); + INFOH(hnd, "answer not JSON (%s).", + state ? UJGetError(state) : ""); goto end; } /* extract the status and error object */ if (UJObjectUnpack(obj, 2, "ON", outer_keys, &o_error, &o_status) < 2) { - ERRH(stmt, "failed to unpack JSON answer (`%.*s`): %s.", - blen, buff, UJGetError(state)); - SET_HDIAG(stmt, SQL_STATE_HY000, MSG_INV_SRV_ANS, 0); + INFOH(hnd, "JSON answer not a SQL error (%s).", UJGetError(state)); goto end; } /* unpack error object */ if (UJObjectUnpack(o_error, 2, "SS", err_keys, &o_type, &o_reason) < 2) { - ERRH(stmt, "failed to unpack error object (`%.*s`): %s.", - blen, buff, UJGetError(state)); - SET_HDIAG(stmt, SQL_STATE_HY000, MSG_INV_SRV_ANS, 0); + INFOH(hnd, "failed to unpack error object (%s).", UJGetError(state)); goto end; } @@ -361,7 +355,7 @@ SQLRETURN TEST_API attach_error(esodbc_stmt_st *stmt, char *buff, size_t blen) wreason = UJReadString(o_reason, &rlen); /* these return empty string in case of mismatch */ assert(wtype && wreason); - DBGH(stmt, "server failures: type: [%zd] `" LWPDL "`, reason: [%zd] `" + DBGH(hnd, "server failures: type: [%zd] `" LWPDL "`, reason: [%zd] `" LWPDL "`, status: %d.", tlen, tlen, wtype, rlen, rlen, wreason, UJNumericInt(o_status)); @@ -370,6 +364,7 @@ SQLRETURN TEST_API attach_error(esodbc_stmt_st *stmt, char *buff, size_t blen) n = swprintf(NULL, 0, MK_WPTR("%.*s: %.*s"), (int)tlen, wtype, (int)rlen, wreason); if (0 < n) { + wbuflen = sizeof(wbuf)/sizeof(*wbuf); wbuflen -= /* ": " */2 + /*\0*/1; tlen = wbuflen < tlen ? wbuflen : tlen; left = wbuflen - tlen; @@ -379,25 +374,48 @@ SQLRETURN TEST_API attach_error(esodbc_stmt_st *stmt, char *buff, size_t blen) n = swprintf(wbuf, wbuflen, MK_WPTR("%.*s: %.*s"), (int)tlen, wtype, (int)rlen, wreason); } - if (n < 0) { - ERRNH(stmt, "failed to print error message from server."); - assert(sizeof(MSG_INV_SRV_ANS) < sizeof(wbuf)); - memcpy(wbuf, MK_WPTR(MSG_INV_SRV_ANS), - sizeof(MSG_INV_SRV_ANS)*sizeof(SQLWCHAR)); + if (n <= 0) { + wbuf[0] = L'\0'; } - post_diagnostic(&stmt->hdr.diag, SQL_STATE_HY000, wbuf, + post_diagnostic(&HDRH(hnd)->diag, SQL_STATE_HY000, wbuf, UJNumericInt(o_status)); + ret = TRUE; end: if (state) { UJFree(state); } - if (buff) { - free(buff); + + return ret; +} + +/* + * Parse an error and push it as statement diagnostic. + */ +SQLRETURN TEST_API attach_error(SQLHANDLE hnd, cstr_st *body, int code) +{ + char buff[SQL_MAX_MESSAGE_LENGTH]; + size_t to_copy; + + ERRH(hnd, "POST failure %d body: len: %zu, content: `%.*s`.", code, + body->cnt, LCSTR(body)); + + if (body->cnt) { + /* try read it as ES/SQL error */ + if (! attach_sql_error(hnd, body)) { + /* if not an ES/SQL failure, attach it as-is (plus \0) */ + to_copy = sizeof(buff) <= body->cnt ? sizeof(buff) - 1 : body->cnt; + memcpy(buff, body->str, to_copy); + buff[to_copy] = '\0'; + + post_c_diagnostic(&HDRH(hnd)->diag, SQL_STATE_08S01, buff, code); + } + + RET_STATE(HDRH(hnd)->diag.state); } - RET_STATE(stmt->hdr.diag.state); + return post_diagnostic(&HDRH(hnd)->diag, SQL_STATE_08S01, NULL, code); } /* diff --git a/driver/queries.h b/driver/queries.h index becfeab6..3953dc4d 100644 --- a/driver/queries.h +++ b/driver/queries.h @@ -12,7 +12,7 @@ void clear_resultset(esodbc_stmt_st *stmt); SQLRETURN TEST_API attach_answer(esodbc_stmt_st *stmt, char *buff, size_t blen); -SQLRETURN TEST_API attach_error(esodbc_stmt_st *stmt, char *buff, size_t blen); +SQLRETURN TEST_API attach_error(SQLHANDLE hnd, cstr_st *body, int code); SQLRETURN TEST_API attach_sql(esodbc_stmt_st *stmt, const SQLWCHAR *sql, size_t tlen); void detach_sql(esodbc_stmt_st *stmt); diff --git a/driver/util.c b/driver/util.c index 98d7db5a..2e6b094a 100644 --- a/driver/util.c +++ b/driver/util.c @@ -255,15 +255,14 @@ void trim_ws(cstr_st *cstr) } /* - * Converts a wchar_t string to a C string for ANSI characters. + * Converts a wchar_t string to a C string for ASCII characters. * 'dst' should be at least as character-long as 'src', if 'src' is * 0-terminated, OR one character longer otherwise (for the 0-term). * 'dst' will always be 0-term'd. * Returns negative if conversion fails, OR number of converted wchars, * including/plus the 0-term. - * */ -int ansi_w2c(const SQLWCHAR *src, char *dst, size_t chars) +int TEST_API ascii_w2c(SQLWCHAR *src, SQLCHAR *dst, size_t chars) { size_t i = 0; @@ -273,11 +272,11 @@ int ansi_w2c(const SQLWCHAR *src, char *dst, size_t chars) assert(chars < INT_MAX); do { - if (CHAR_MAX < src[i]) { + if (SCHAR_MAX < src[i]) { return -((int)i + 1); } - dst[i] = (char)src[i]; - } while (src[i] && (++i < chars)); + dst[i] = (SQLCHAR)src[i]; + } while ((src[i] != L'\0') && (++i < chars)); if (chars <= i) { /* equiv to: (src[i] != 0) */ /* loop stopped b/c of length -> src is not 0-term'd */ @@ -286,6 +285,32 @@ int ansi_w2c(const SQLWCHAR *src, char *dst, size_t chars) return (int)i + 1; } +/* + * This is the inverse of ascii_w2c(). + */ +int TEST_API ascii_c2w(SQLCHAR *src, SQLWCHAR *dst, size_t chars) +{ + size_t i = 0; + + if (chars < 1) { + return -1; + } + assert(chars < INT_MAX); + + do { + if (src[i] < 0) { + return -((int)i + 1); + } + dst[i] = (SQLWCHAR)src[i]; + } while ((src[i] != '\0') && (++i < chars)); + + if (chars <= i) { /* equiv to: (src[i] != 0) */ + /* loop stopped b/c of length -> src is not 0-term'd */ + dst[i] = L'\0'; /* chars + 1 <= [dst] */ + } + return (int)i + 1; +} + int wmemncasecmp(const SQLWCHAR *a, const SQLWCHAR *b, size_t len) { size_t i; diff --git a/driver/util.h b/driver/util.h index 5d8bb0e4..e08f2efb 100644 --- a/driver/util.h +++ b/driver/util.h @@ -72,9 +72,15 @@ typedef struct cstr { } cstr_st; /* - * Copy converted strings from SQLWCHAR to char, for ANSI strings. + * Converts a wchar_t string to a C string for ASCII characters. + * 'dst' should be at least as character-long as 'src', if 'src' is + * 0-terminated, OR one character longer otherwise (for the 0-term). + * 'dst' will always be 0-term'd. + * Returns negative if conversion fails, OR number of converted wchars, + * including/plus the 0-term. */ -int ansi_w2c(const SQLWCHAR *src, char *dst, size_t chars); +int TEST_API ascii_w2c(SQLWCHAR *src, SQLCHAR *dst, size_t chars); +int TEST_API ascii_c2w(SQLCHAR *src, SQLWCHAR *dst, size_t chars); /* * Compare two SQLWCHAR object, case INsensitive. */ diff --git a/test/test_queries.cc b/test/test_queries.cc new file mode 100644 index 00000000..00dd679c --- /dev/null +++ b/test/test_queries.cc @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +extern "C" { +#include "queries.h" +} // extern C + +#include + + +namespace test { + +class Queries : public ::testing::Test { +}; + +TEST_F(Queries, attach_error_sql) { +#undef SRC_STR +#undef SRC_AID1 +#undef SRC_AID2 +#define SRC_AID1 "parsing_exception" +#define SRC_AID2 "line 1:8: identifiers must not etc." +#define SRC_AID3 400 +#define SRC_STR \ +"{\ + \"error\": {\ + \"root_cause\": [\ + {\ + \"type\": \"" SRC_AID1 "\",\ + \"reason\": \"" SRC_AID2 "\"\ + }\ + ],\ + \"type\": \"parsing_exception\",\ + \"reason\": \"line 1:8: identifiers must not start with a digit; please use double quotes\"\ + },\ + \"status\": " STR(SRC_AID3) "\ +}" + + esodbc_stmt_st stmt; + SQLRETURN ret; + cstr_st body = CSTR_INIT(SRC_STR); + + memset(&stmt, 0, sizeof(stmt)); + ret = attach_error(&stmt, &body, 400); + ASSERT_EQ(ret, SQL_ERROR); + + ASSERT_EQ(stmt.hdr.diag.state, SQL_STATE_HY000); + ASSERT_STREQ((wchar_t *)stmt.hdr.diag.text, + (wchar_t *)MK_WPTR(SRC_AID1 ": " SRC_AID2)); + ASSERT_EQ(stmt.hdr.diag.native_code, SRC_AID3); +} + +TEST_F(Queries, attach_error_non_sql) { +#undef SRC_STR +#undef SRC_AID1 +#undef SRC_AID2 +#undef SRC_AID3 +#define SRC_AID1 "parsing_exception" +#define SRC_AID2 "line 1:8: identifiers must not etc." +#define SRC_AID3 400 +#define SRC_STR \ +"{\ + \"error\": {\ + \"root_cause\": [\ + {\ + \"tyXXXpe\": \"" SRC_AID1 "\",\ + \"reason\": \"" SRC_AID2 "\"\ + }\ + ],\ + \"type\": \"parsing_exception\",\ + \"reason\": \"line 1:8: identifiers must not start with a digit; please use double quotes\"\ + },\ + \"status\": " STR(SRC_AID3) "\ +}" + + esodbc_stmt_st stmt; + SQLRETURN ret; + cstr_st body = CSTR_INIT(SRC_STR); + + memset(&stmt, 0, sizeof(stmt)); + ret = attach_error(&stmt, &body, 400); + ASSERT_EQ(ret, SQL_ERROR); + + ASSERT_EQ(stmt.hdr.diag.state, SQL_STATE_08S01); + ASSERT_STREQ((wchar_t *)stmt.hdr.diag.text, + (wchar_t *)MK_WPTR(SRC_STR)); + ASSERT_EQ(stmt.hdr.diag.native_code, SRC_AID3); +} + + +} // test namespace + +/* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : */ diff --git a/test/test_util.cc b/test/test_util.cc index 4574ae2c..1c506049 100644 --- a/test/test_util.cc +++ b/test/test_util.cc @@ -105,6 +105,32 @@ TEST_F(Util, wstr_to_utf8_no_nts) { free(dst.str); } +TEST_F(Util, ascii_c2w2c) +{ +#undef SRC_STR +#define SRC_STR "abcd" + SQLCHAR *test = (SQLCHAR *)SRC_STR; + SQLWCHAR wbuff[2 * sizeof(SRC_STR)] = {(SQLWCHAR)-1}; + SQLCHAR cbuff[2 * sizeof(SRC_STR)] = {(SQLCHAR)-1}; + int c2w, w2c; + + c2w = ascii_c2w(test, wbuff, sizeof(wbuff)/sizeof(*wbuff)); + w2c = ascii_w2c(wbuff, cbuff, sizeof(cbuff)); + ASSERT_EQ(c2w, w2c); + ASSERT_STREQ((char *)test, (char *)cbuff); +} + +TEST_F(Util, ascii_c2w_add_0term) +{ +#undef SRC_STR +#define SRC_STR "abcd" + SQLCHAR *test = (SQLCHAR *)SRC_STR; + SQLWCHAR wbuff[sizeof(SRC_STR)] = {(SQLWCHAR)-1}; + SQLWCHAR *wtest = MK_WPTR(SRC_STR); + + ASSERT_EQ(ascii_c2w(test, wbuff, sizeof(SRC_STR) - 1), sizeof(SRC_STR)); + ASSERT_STREQ((wchar_t *)wbuff, (wchar_t *)wtest); +} } // test namespace From 507971e7313ddc494a673d309da730ed289bf347 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Mon, 17 Sep 2018 23:20:43 +0200 Subject: [PATCH 5/7] fixed attach_error tests --- test/test_queries.cc | 52 +++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/test/test_queries.cc b/test/test_queries.cc index 00dd679c..919646a3 100644 --- a/test/test_queries.cc +++ b/test/test_queries.cc @@ -8,12 +8,13 @@ extern "C" { #include "queries.h" } // extern C +#include "connected_dbc.h" #include namespace test { -class Queries : public ::testing::Test { +class Queries : public ::testing::Test, public ConnectedDBC { }; TEST_F(Queries, attach_error_sql) { @@ -32,24 +33,27 @@ TEST_F(Queries, attach_error_sql) { \"reason\": \"" SRC_AID2 "\"\ }\ ],\ - \"type\": \"parsing_exception\",\ - \"reason\": \"line 1:8: identifiers must not start with a digit; please use double quotes\"\ + \"type\": \"" SRC_AID1 "\",\ + \"reason\": \"" SRC_AID2 "\"\ },\ \"status\": " STR(SRC_AID3) "\ }" - esodbc_stmt_st stmt; SQLRETURN ret; cstr_st body = CSTR_INIT(SRC_STR); + SQLWCHAR *pos, *prev; - memset(&stmt, 0, sizeof(stmt)); - ret = attach_error(&stmt, &body, 400); + ret = attach_error(stmt, &body, 400); ASSERT_EQ(ret, SQL_ERROR); - ASSERT_EQ(stmt.hdr.diag.state, SQL_STATE_HY000); - ASSERT_STREQ((wchar_t *)stmt.hdr.diag.text, - (wchar_t *)MK_WPTR(SRC_AID1 ": " SRC_AID2)); - ASSERT_EQ(stmt.hdr.diag.native_code, SRC_AID3); + ASSERT_EQ(HDRH(stmt)->diag.state, SQL_STATE_HY000); + /* skip over ODBC's error message format prefix */ + for (pos = HDRH(stmt)->diag.text; pos; pos = wcschr(prev, L']')) { + prev = pos + 1; + } + ASSERT_TRUE(prev != NULL); + ASSERT_STREQ((wchar_t *)prev, (wchar_t *)MK_WPTR(SRC_AID1 ": " SRC_AID2)); + ASSERT_EQ(HDRH(stmt)->diag.native_code, SRC_AID3); } TEST_F(Queries, attach_error_non_sql) { @@ -63,30 +67,28 @@ TEST_F(Queries, attach_error_non_sql) { #define SRC_STR \ "{\ \"error\": {\ - \"root_cause\": [\ - {\ - \"tyXXXpe\": \"" SRC_AID1 "\",\ - \"reason\": \"" SRC_AID2 "\"\ - }\ - ],\ - \"type\": \"parsing_exception\",\ - \"reason\": \"line 1:8: identifiers must not start with a digit; please use double quotes\"\ + \"root_cause\": \"" SRC_AID1 "\", \ + \"tyXXXpe\": \"" SRC_AID1 "\",\ + \"reason\": \"" SRC_AID2 "\"\ },\ \"status\": " STR(SRC_AID3) "\ }" - esodbc_stmt_st stmt; SQLRETURN ret; cstr_st body = CSTR_INIT(SRC_STR); + SQLWCHAR *pos, *prev; - memset(&stmt, 0, sizeof(stmt)); - ret = attach_error(&stmt, &body, 400); + ret = attach_error(stmt, &body, 400); ASSERT_EQ(ret, SQL_ERROR); - ASSERT_EQ(stmt.hdr.diag.state, SQL_STATE_08S01); - ASSERT_STREQ((wchar_t *)stmt.hdr.diag.text, - (wchar_t *)MK_WPTR(SRC_STR)); - ASSERT_EQ(stmt.hdr.diag.native_code, SRC_AID3); + ASSERT_EQ(HDRH(stmt)->diag.state, SQL_STATE_08S01); + /* skip over ODBC's error message format prefix */ + for (pos = HDRH(stmt)->diag.text; pos; pos = wcschr(prev, L']')) { + prev = pos + 1; + } + ASSERT_TRUE(prev != NULL); + ASSERT_STREQ(prev, (wchar_t *)MK_WPTR(SRC_STR)); + ASSERT_EQ(HDRH(stmt)->diag.native_code, SRC_AID3); } From 8c4c99ce2712b29c6881df8adabfcd18617b697f Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Wed, 19 Sep 2018 16:14:00 +0200 Subject: [PATCH 6/7] Always use (CURLAUTH_)BASIC auth method - removed the non-SSL case where _ANY would have been used. - fix a spelling error ('revokation'); - log the correct upper 'secure' (parameter) limit in error message. --- driver/connect.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/driver/connect.c b/driver/connect.c index 227c11cd..b984f662 100644 --- a/driver/connect.c +++ b/driver/connect.c @@ -301,7 +301,7 @@ static SQLRETURN dbc_curl_init(esodbc_dbc_st *dbc) ERRH(dbc, "libcurl: failed to enable host check."); goto err; } - /* verify the revokation chain? */ + /* verify the revocation chain? */ res = curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, ESODBC_SEC_CHECK_REVOKE <= dbc->secure ? 0L : CURLSSLOPT_NO_REVOKE); @@ -316,10 +316,8 @@ static SQLRETURN dbc_curl_init(esodbc_dbc_st *dbc) if (dbc->uid.cnt) { /* set the authentication methods: * "basic" is currently - 7.0.0 - the only supported method */ - res = curl_easy_setopt(curl, CURLOPT_HTTPAUTH, - /* libcurl (7.61.0) won't pick Basic auth over SSL when - * _ANY is used. -- ??? XXX */ - dbc->secure ? CURLAUTH_BASIC : CURLAUTH_ANY); + /* Note: libcurl (7.61.0) won't pick Basic auth over SSL with _ANY */ + res = curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); if (res != CURLE_OK) { ERRH(dbc, "libcurl: failed to set HTTP auth methods."); goto err; @@ -616,7 +614,7 @@ static SQLRETURN process_config(esodbc_dbc_st *dbc, esodbc_dsn_attrs_st *attrs) } if (secure < ESODBC_SEC_NONE || ESODBC_SEC_MAX <= secure) { ERRH(dbc, "invalid secure param `" LWPDL "` (not within %d - %d).", - LWSTR(&attrs->secure), ESODBC_SEC_NONE, ESODBC_SEC_CHECK_HOST); + LWSTR(&attrs->secure), ESODBC_SEC_NONE, ESODBC_SEC_MAX - 1); goto err; } else { dbc->secure = (long)secure; From de3b9b99d7d24fe7a349188d4d5f838e695643f3 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Wed, 19 Sep 2018 17:22:23 +0200 Subject: [PATCH 7/7] report a user without a password as an error - however, allow the call to proceed. --- driver/connect.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/driver/connect.c b/driver/connect.c index b984f662..b7b22a61 100644 --- a/driver/connect.c +++ b/driver/connect.c @@ -336,8 +336,9 @@ static SQLRETURN dbc_curl_init(esodbc_dbc_st *dbc) goto err; } } else { - INFOH(dbc, "no password for username `" LCPDL "`.", - LCSTR(&dbc->uid)); + /* not an error per se, but make it always visible */ + ERRH(dbc, "no password provided for username `" LCPDL "`! " + "Intended?", LCSTR(&dbc->uid)); } if (dbc->follow) { /* restrict sharing credentials to first contacted host? */