diff --git a/.gitignore b/.gitignore index 5e260e24..4040d94a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ *.swp driver/*.swp +builds/* cscope.out +.vs diff --git a/CMakeLists.txt b/CMakeLists.txt index b739b35e..61ca11ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -208,7 +208,6 @@ add_custom_target(curlclean WORKING_DIRECTORY "${LIBCURL_PATH_SRC}/winbuild" ) - message("Driver source files: ${DRV_SRC} .") message("Driver include paths: " ${ODBC_INC} ${DRV_SRC_DIR} ${LIBCURL_INC_PATH} ${UJSON4C_INC} ${CTIMESTAMP_PATH_SRC}) @@ -223,7 +222,7 @@ target_compile_definitions(${DRV_NAME} PRIVATE "DRIVER_BUILD") include_directories(${ODBC_INC} ${DRV_SRC_DIR} ${LIBCURL_INC_PATH} ${UJSON4C_INC} ${CTIMESTAMP_PATH_SRC}) -target_link_libraries(${DRV_NAME} libcurl) +target_link_libraries(${DRV_NAME} odbccp32 legacy_stdio_definitions libcurl) # add testing project/target enable_testing() diff --git a/build.bat b/build.bat index 75016f91..1f110d7f 100644 --- a/build.bat +++ b/build.bat @@ -231,7 +231,7 @@ REM USAGE function: output a usage message echo setup : invoke MSVC's build environment setup script before echo building (requires 2017 version or later^). echo clean : remove all the files in the build dir. - echo proper : clean both the libs and builds dirs and exit. + echo proper : clean libs, builds and project dirs and exit. echo nobuild : skip project building (the default is to build^). echo type=T : selects the build type; T can be one of Debug/Release/ echo RelWithDebInfo/MinSizeRel^); defaults to Debug. @@ -276,6 +276,8 @@ REM PROPER function: clean up the build and libs dir. MSBuild %BUILD_DIR%\curlclean.vcxproj ) call:CLEAN + REM delete VisualStudio files + rmdir /S /Q .vs goto:eof @@ -385,7 +387,7 @@ REM BUILD function: build various targets REM file name expansion, cmd style... for /f %%i in ("%DRIVER_BASE_NAME%*.vcxproj") do MSBuild %%~nxi %MSBUILD_ARGS% - if not ERRORLEVEL 1 if /i not [%ARG:symbols=%] == [%ARG%] ( + if not ERRORLEVEL 1 if /i not [%ARG:exports=%] == [%ARG%] ( dumpbin /exports %CFG_INTDIR%\%DRIVER_BASE_NAME%*.dll ) ) diff --git a/builds/.gitignore b/builds/.gitignore deleted file mode 100644 index d6b7ef32..00000000 --- a/builds/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/devtools/astyle-c.sh b/devtools/astyle-c.sh index 3c3984e1..300af222 100755 --- a/devtools/astyle-c.sh +++ b/devtools/astyle-c.sh @@ -20,7 +20,7 @@ fi # Ensure astyle is available and is of the right version to have all below # parameters supported. # -if ! which -s astyle ; then +if ! which astyle &>/dev/null ; then echo "ERROR: The astyle code formatter is not available. Exiting." exit 1 fi diff --git a/driver/build_def.bat b/driver/build_def.bat index becdd8d7..d32eb914 100644 --- a/driver/build_def.bat +++ b/driver/build_def.bat @@ -3,7 +3,7 @@ REM Helper script to generate the definition file of symbols to export in DLL @echo off REM TODO: find out a smart way for this filtering -set FILTER="Select-String -Pattern 'SQLRETURN\s+SQL_API\s+\w+' *.c | %%{ [regex]::split($_, '\s+')[2]; } | %%{ [regex]::split($_, '\(')[0]; }" +set FILTER="Select-String -Pattern '^\w+\s+SQL_API\s+\w+(\(|$)' *.c | %%{ [regex]::split($_, '\s+')[2]; } | %%{ [regex]::split($_, '\(')[0]; }" set DRV_NAME=%1 set OUTFILE=%2 diff --git a/driver/catalogue.c b/driver/catalogue.c index 21404093..a7dd541b 100644 --- a/driver/catalogue.c +++ b/driver/catalogue.c @@ -159,6 +159,9 @@ size_t quote_tokens(SQLWCHAR *src, size_t len, SQLWCHAR *dest) } *pos ++ = src[i]; } + if (copying) { + *pos ++ = L'\''; /* end last token */ + } /* should not overrun */ assert(i < 2/*see typ_buf below*/ * ESODBC_MAX_IDENTIFIER_LEN); return pos - dest; diff --git a/driver/connect.c b/driver/connect.c index 563884f5..620d80ea 100644 --- a/driver/connect.c +++ b/driver/connect.c @@ -15,6 +15,7 @@ #include "log.h" #include "info.h" #include "util.h" +#include "dsn.h" /* HTTP headers default for every request */ #define HTTP_ACCEPT_JSON "Accept: application/json" @@ -47,54 +48,6 @@ #define JSON_COL_SCALED_FLOAT "scaled_float" -/* attribute keywords used in connection strings */ -#define CONNSTR_KW_DRIVER "Driver" -#define CONNSTR_KW_DSN "DSN" -#define CONNSTR_KW_PWD "PWD" -#define CONNSTR_KW_UID "UID" -#define CONNSTR_KW_SAVEFILE "SAVEFILE" -#define CONNSTR_KW_FILEDSN "FILEDSN" -#define CONNSTR_KW_SERVER "Server" -#define CONNSTR_KW_PORT "Port" -#define CONNSTR_KW_SECURE "Secure" -#define CONNSTR_KW_TIMEOUT "Timeout" -#define CONNSTR_KW_FOLLOW "Follow" -#define CONNSTR_KW_CATALOG "Catalog" -#define CONNSTR_KW_PACKING "Packing" -#define CONNSTR_KW_MAX_FETCH_SIZE "MaxFetchSize" -#define CONNSTR_KW_MAX_BODY_SIZE_MB "MaxBodySizeMB" - -#define ODBC_REG_SUBKEY_PATH "SOFTWARE\\ODBC\\ODBC.INI" -#define REG_HKLM "HKEY_LOCAL_MACHINE" -#define REG_HKCU "HKEY_CURRENT_USER" - -/* max length of a registry key value name */ -#define MAX_REG_VAL_NAME 1024 -/* max size of a registry key data */ -#define MAX_REG_DATA_SIZE 4096 - - -/* stucture to collect all attributes in a connection string */ -typedef struct { - wstr_st driver; - wstr_st dsn; - wstr_st pwd; - wstr_st uid; - wstr_st savefile; - wstr_st filedsn; - wstr_st server; - wstr_st port; - wstr_st secure; - wstr_st timeout; - wstr_st follow; - wstr_st catalog; - wstr_st packing; - wstr_st max_fetch_size; - wstr_st max_body_size; - wstr_st trace_file; - wstr_st trace_level; -} config_attrs_st; - /* structure for one row returned by the ES. * This is a mirror of elasticsearch_type, with length-or-indicator fields * for each of the members in elasticsearch_type */ @@ -473,361 +426,10 @@ static SQLRETURN test_connect(CURL *curl) return SQL_SUCCESS; } -static BOOL assign_config_attr(config_attrs_st *attrs, - wstr_st *keyword, wstr_st *value, BOOL overwrite) -{ - struct { - wstr_st *kw; - wstr_st *val; - } *iter, map[] = { - {&MK_WSTR(CONNSTR_KW_DRIVER), &attrs->driver}, - {&MK_WSTR(CONNSTR_KW_DSN), &attrs->dsn}, - {&MK_WSTR(CONNSTR_KW_PWD), &attrs->pwd}, - {&MK_WSTR(CONNSTR_KW_UID), &attrs->uid}, - {&MK_WSTR(CONNSTR_KW_SAVEFILE), &attrs->savefile}, - {&MK_WSTR(CONNSTR_KW_FILEDSN), &attrs->filedsn}, - {&MK_WSTR(CONNSTR_KW_SERVER), &attrs->server}, - {&MK_WSTR(CONNSTR_KW_PORT), &attrs->port}, - {&MK_WSTR(CONNSTR_KW_SECURE), &attrs->secure}, - {&MK_WSTR(CONNSTR_KW_TIMEOUT), &attrs->timeout}, - {&MK_WSTR(CONNSTR_KW_FOLLOW), &attrs->follow}, - {&MK_WSTR(CONNSTR_KW_CATALOG), &attrs->catalog}, - {&MK_WSTR(CONNSTR_KW_PACKING), &attrs->packing}, - {&MK_WSTR(CONNSTR_KW_MAX_FETCH_SIZE), &attrs->max_fetch_size}, - {&MK_WSTR(CONNSTR_KW_MAX_BODY_SIZE_MB), &attrs->max_body_size}, - {NULL, NULL} - }; - - for (iter = &map[0]; iter->kw; iter ++) { - if (! EQ_CASE_WSTR(iter->kw, keyword)) { - continue; - } - /* it's a match: has it been assigned already? */ - if (iter->val->cnt) { - if (! overwrite) { - INFO("multiple occurances of keyword '" LWPDL "'; " - "ignoring new `" LWPDL "`, keeping `" LWPDL "`.", - LWSTR(iter->kw), LWSTR(value), LWSTR(iter->val)); - continue; - } - INFO("multiple occurances of keyword '" LWPDL "'; " - "overwriting old `" LWPDL "` with new `" LWPDL "`.", - LWSTR(iter->kw), LWSTR(iter->val), LWSTR(value)); - } - *iter->val = *value; - return TRUE; - } - - return FALSE; -} - -/* - * Advance position in string, skipping white space. - * if exended is true, `;` will be treated as white space too. - */ -static SQLWCHAR *skip_ws(SQLWCHAR **pos, SQLWCHAR *end, BOOL extended) -{ - while (*pos < end) { - switch(**pos) { - case ' ': - case '\t': - case '\r': - case '\n': - (*pos)++; - break; - - case '\0': - return NULL; - - case ';': - if (extended) { - (*pos)++; - break; - } - // no break; - - default: - return *pos; - } - } - - /* end of string reached */ - return NULL; -} - -/* - * Parse a keyword or a value. - * Within braces, any character is allowed, safe for \0 (i.e. no "bla{bla\0" - * is supported as keyword or value). - * Braces within braces are allowed. - */ -static BOOL parse_token(BOOL is_value, SQLWCHAR **pos, SQLWCHAR *end, - wstr_st *token) -{ - BOOL brace_escaped = FALSE; - int open_braces = 0; - SQLWCHAR *start = *pos; - BOOL stop = FALSE; - - while (*pos < end && (! stop)) { - switch (**pos) { - case '\\': - if (! is_value) { - ERR("keywords and data source names cannot contain " - "the backslash."); - return FALSE; - } - (*pos)++; - break; - - case ' ': - case '\t': - case '\r': - case '\n': - if (open_braces || is_value) { - (*pos)++; - } else { - stop = TRUE; - } - break; - - case '=': - if (open_braces || is_value) { - (*pos)++; - } else { - stop = TRUE; - } - break; - - case ';': - if (open_braces) { - (*pos)++; - } else if (is_value) { - stop = TRUE; - } else { - ERR("';' found while parsing keyword"); - return FALSE; - } - break; - - case '\0': - if (open_braces) { - ERR("null terminator found while within braces"); - return FALSE; - } else if (! is_value) { - ERR("null terminator found while parsing keyword."); - return FALSE; - } /* else: \0 used as delimiter of value string */ - stop = TRUE; - break; - - case '{': - if (*pos == start) { - open_braces ++; - } else if (open_braces) { - ERR("token started with opening brace, so can't use " - "inner braces"); - /* b/c: `{foo{ = }}bar} = val`; `foo{bar}baz` is fine */ - return FALSE; - } - (*pos)++; - break; - - case '}': - if (open_braces) { - open_braces --; - brace_escaped = TRUE; - stop = TRUE; - } - (*pos)++; - break; - - default: - (*pos)++; - } - } - - if (open_braces) { - ERR("string finished with open braces."); - return FALSE; - } - - token->str = start + (brace_escaped ? 1 : 0); - token->cnt = (*pos - start) - (brace_escaped ? 2 : 0); - return TRUE; -} - -static SQLWCHAR *parse_separator(SQLWCHAR **pos, SQLWCHAR *end) -{ - if (*pos < end) { - if (**pos == '=') { - (*pos)++; - return *pos; - } - } - return NULL; -} - -/* - * - "keywords and attribute values that contain the characters []{}(),;?*=!@ - * not enclosed with braces should be avoided"; => allowed. - * - "value of the DSN keyword cannot consist only of blanks and should not - * contain leading blanks"; - * - "keywords and data source names cannot contain the backslash (\) - * character."; - * - "value enclosed with braces ({}) containing any of the characters - * []{}(),;?*=!@ is passed intact to the driver."; - * - * foo{bar}=baz=foo; => "foo{bar}" = "baz=foo" - * - * * `=` is delimiter, unless within {} - * * `{` and `}` allowed within {} - * * brances need to be returned to out-str; - */ -static BOOL parse_connection_string(config_attrs_st *attrs, - SQLWCHAR *szConnStrIn, SQLSMALLINT cchConnStrIn) -{ - - SQLWCHAR *pos; - SQLWCHAR *end; - wstr_st keyword, value; - - /* parse and assign attributes in connection string */ - pos = szConnStrIn; - end = pos + (cchConnStrIn == SQL_NTS ? SHRT_MAX : cchConnStrIn); - - while (skip_ws(&pos, end, TRUE)) { - if (! parse_token(FALSE, &pos, end, &keyword)) { - ERR("failed to parse keyword at position %zd", - pos - szConnStrIn); - return FALSE; - } - - if (! skip_ws(&pos, end, FALSE)) { - return FALSE; - } - - if (! parse_separator(&pos, end)) { - ERR("failed to parse separator (`=`) at position %zd", - pos - szConnStrIn); - return FALSE; - } - - if (! skip_ws(&pos, end, FALSE)) { - return FALSE; - } - - if (! parse_token(TRUE, &pos, end, &value)) { - ERR("failed to parse value at position %zd", - pos - szConnStrIn); - return FALSE; - } - - DBG("read connection string attribute: `" LWPDL "` = `" LWPDL - "`.", LWSTR(&keyword), LWSTR(&value)); - if (! assign_config_attr(attrs, &keyword, &value, TRUE)) - WARN("keyword '" LWPDL "' is unknown, ignoring it.", - LWSTR(&keyword)); - } - - return TRUE; -} - -static inline BOOL needs_braces(wstr_st *token) -{ - int i; - - for (i = 0; i < token->cnt; i ++) { - switch(token->str[i]) { - case ' ': - case '\t': - case '\r': - case '\n': - case '=': - case ';': - return TRUE; - } - } - return FALSE; -} - -/* build a connection string to be written in the DSN files */ -static BOOL write_connection_string(config_attrs_st *attrs, - SQLWCHAR *szConnStrOut, SQLSMALLINT cchConnStrOutMax, - SQLSMALLINT *pcchConnStrOut) -{ - int n, braces; - size_t pos; - wchar_t *format; - struct { - wstr_st *val; - char *kw; - } *iter, map[] = { - {&attrs->driver, CONNSTR_KW_DRIVER}, - {&attrs->dsn, CONNSTR_KW_DSN}, - {&attrs->pwd, CONNSTR_KW_PWD}, - {&attrs->uid, CONNSTR_KW_UID}, - {&attrs->savefile, CONNSTR_KW_SAVEFILE}, - {&attrs->filedsn, CONNSTR_KW_FILEDSN}, - {&attrs->server, CONNSTR_KW_SERVER}, - {&attrs->port, CONNSTR_KW_PORT}, - {&attrs->secure, CONNSTR_KW_SECURE}, - {&attrs->timeout, CONNSTR_KW_TIMEOUT}, - {&attrs->follow, CONNSTR_KW_FOLLOW}, - {&attrs->catalog, CONNSTR_KW_CATALOG}, - {&attrs->packing, CONNSTR_KW_PACKING}, - {&attrs->max_fetch_size, CONNSTR_KW_MAX_FETCH_SIZE}, - {&attrs->max_body_size, CONNSTR_KW_MAX_BODY_SIZE_MB}, - {NULL, NULL} - }; - - for (iter = &map[0], pos = 0; iter->val; iter ++) { - if (iter->val->cnt) { - braces = needs_braces(iter->val) ? 2 : 0; - if (cchConnStrOutMax && szConnStrOut) { - /* swprintf will fail if formated string would overrun the - * buffer size */ - if (cchConnStrOutMax - pos < iter->val->cnt + braces) { - /* indicate that we've reached buffer limits: only account - * for how long the string would be */ - cchConnStrOutMax = 0; - pos += iter->val->cnt + braces; - continue; - } - if (braces) { - format = WPFCP_DESC "={" WPFWP_LDESC "};"; - } else { - format = WPFCP_DESC "=" WPFWP_LDESC ";"; - } - n = swprintf(szConnStrOut + pos, cchConnStrOutMax - pos, - format, iter->kw, LWSTR(iter->val)); - if (n < 0) { - ERRN("failed to outprint connection string (space " - "left: %d; needed: %d).", cchConnStrOutMax - pos, - iter->val->cnt); - return FALSE; - } else { - pos += n; - } - } else { - /* simply increment the counter, since the untruncated length - * need to be returned to the app */ - pos += iter->val->cnt + braces; - } - } - } - - *pcchConnStrOut = (SQLSMALLINT)pos; - - DBG("Output connection string: `" LWPD "`; out len: %d.", - szConnStrOut, pos); - return TRUE; -} - /* * init dbc from configured attributes */ -static SQLRETURN process_config(esodbc_dbc_st *dbc, config_attrs_st *attrs) +static SQLRETURN process_config(esodbc_dbc_st *dbc, esodbc_dsn_attrs_st *attrs) { esodbc_state_et state = SQL_STATE_HY000; int n, cnt; @@ -879,8 +481,8 @@ static SQLRETURN process_config(esodbc_dbc_st *dbc, config_attrs_st *attrs) * request timeout for liburl: negative reset to 0 */ if (! str2bigint(&attrs->timeout, /*wide?*/TRUE, (SQLBIGINT *)&timeout)) { - ERRH(dbc, "failed to convert '" LWPDL "' to big int.", - LWSTR(&attrs->timeout)); + ERRH(dbc, "failed to convert `" LWPDL "` [%zu] to big int.", + LWSTR(&attrs->timeout), attrs->timeout.cnt); goto err; } if (timeout < 0) { @@ -895,13 +497,13 @@ static SQLRETURN process_config(esodbc_dbc_st *dbc, config_attrs_st *attrs) */ if (! str2bigint(&attrs->max_body_size, /*wide?*/TRUE, (SQLBIGINT *)&max_body_size)) { - ERRH(dbc, "failed to convert '" LWPDL "' to long long.", - LWSTR(&attrs->max_body_size)); + ERRH(dbc, "failed to convert `" LWPDL "` [%zu] to long long.", + LWSTR(&attrs->max_body_size), attrs->max_body_size.cnt); goto err; } if (max_body_size < 0) { ERRH(dbc, "'%s' setting can't be negative (%ld).", - CONNSTR_KW_MAX_BODY_SIZE_MB, max_body_size); + ESODBC_DSN_MAX_BODY_SIZE_MB, max_body_size); goto err; } else { dbc->amax = max_body_size * 1024 * 1024; @@ -913,13 +515,13 @@ static SQLRETURN process_config(esodbc_dbc_st *dbc, config_attrs_st *attrs) */ if (! str2bigint(&attrs->max_fetch_size, /*wide?*/TRUE, (SQLBIGINT *)&max_fetch_size)) { - ERRH(dbc, "failed to convert '" LWPDL "' to long long.", - LWSTR(&attrs->max_fetch_size)); + ERRH(dbc, "failed to convert `" LWPDL "` [%zu] to long long.", + LWSTR(&attrs->max_fetch_size), attrs->max_fetch_size.cnt); goto err; } if (max_fetch_size < 0) { ERRH(dbc, "'%s' setting can't be negative (%ld).", - CONNSTR_KW_MAX_FETCH_SIZE, max_fetch_size); + ESODBC_DSN_MAX_FETCH_SIZE, max_fetch_size); goto err; } else { dbc->fetch.max = max_fetch_size; @@ -961,43 +563,6 @@ static SQLRETURN process_config(esodbc_dbc_st *dbc, config_attrs_st *attrs) RET_HDIAGS(dbc, state); } -static inline void assign_defaults(config_attrs_st *attrs) -{ - /* assign defaults where not assigned and applicable */ - if (! attrs->server.cnt) { - attrs->server = MK_WSTR(ESODBC_DEF_SERVER); - } - if (! attrs->port.cnt) { - attrs->port = MK_WSTR(ESODBC_DEF_PORT); - } - if (! attrs->secure.cnt) { - attrs->secure = MK_WSTR(ESODBC_DEF_SECURE); - } - if (! attrs->timeout.cnt) { - attrs->timeout = MK_WSTR(ESODBC_DEF_TIMEOUT); - } - if (! attrs->follow.cnt) { - attrs->follow = MK_WSTR(ESODBC_DEF_FOLLOW); - } - - /* no default packing */ - - if (! attrs->packing.cnt) { - attrs->packing = MK_WSTR(ESODBC_DEF_PACKING); - } - if (! attrs->max_fetch_size.cnt) { - attrs->max_fetch_size = MK_WSTR(ESODBC_DEF_FETCH_SIZE); - } - if (! attrs->max_body_size.cnt) { - attrs->max_body_size = MK_WSTR(ESODBC_DEF_MAX_BODY_SIZE_MB); - } - - /* default: no trace file */ - - if (! attrs->trace_level.cnt) { - attrs->trace_level = MK_WSTR(ESODBC_DEF_TRACE_LEVEL); - } -} /* release all resources, except the handler itself */ void cleanup_dbc(esodbc_dbc_st *dbc) @@ -1032,7 +597,7 @@ void cleanup_dbc(esodbc_dbc_st *dbc) cleanup_curl(dbc); } -static SQLRETURN do_connect(esodbc_dbc_st *dbc, config_attrs_st *attrs) +static SQLRETURN do_connect(esodbc_dbc_st *dbc, esodbc_dsn_attrs_st *attrs) { SQLRETURN ret; char *url = NULL; @@ -1732,120 +1297,6 @@ static BOOL load_es_types(esodbc_dbc_st *dbc) return ret; } -#if defined(_WIN32) || defined (WIN32) -/* - * Reads system registry for ODBC DSN subkey named in attrs->dsn. - */ -static BOOL read_system_info(config_attrs_st *attrs, TCHAR *buff) -{ - HKEY hkey; - BOOL ret = FALSE; - const char *ktree; - DWORD valsno, i, j; /* number of values in subkey */ - DWORD maxvallen; /* len of longest value name */ - DWORD maxdatalen; /* len of longest data (buffer) */ - TCHAR val[MAX_REG_VAL_NAME]; - DWORD vallen; - DWORD valtype; - BYTE *d; - DWORD datalen; - tstr_st tval, tdata; - - if (swprintf(val, sizeof(val)/sizeof(val[0]), WPFCP_DESC "\\" WPFWP_LDESC, - ODBC_REG_SUBKEY_PATH, LWSTR(&attrs->dsn)) < 0) { - ERRN("failed to print registry key path."); - return FALSE; - } - /* try accessing local user's config first, if that fails, systems' */ - if (RegOpenKeyEx(HKEY_CURRENT_USER, val, /*options*/0, KEY_READ, - &hkey) != ERROR_SUCCESS) { - INFO("failed to open registry key `" REG_HKCU "\\" LWPD "`.", - val); - if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, val, /*options*/0, KEY_READ, - &hkey) != ERROR_SUCCESS) { - INFO("failed to open registry key `" REG_HKLM "\\" LWPD "`.", - val); - goto end; - } else { - ktree = REG_HKLM; - } - } else { - ktree = REG_HKCU; - } - - if (RegQueryInfoKey(hkey, /*key class*/NULL, /*len of key class*/NULL, - /*reserved*/NULL, /*no subkeys*/NULL, /*longest subkey*/NULL, - /*longest subkey class name*/NULL, &valsno, &maxvallen, - &maxdatalen, /*sec descr*/NULL, - /*update time */NULL) != ERROR_SUCCESS) { - ERRN("Failed to query registery key info for path `%s\\" LWPD - "`.", ktree, val); - goto end; - } else { - DBG("Subkey '%s\\" LWPD "': vals: %d, lengthiest name: %d, " - "lengthiest data: %d.", ktree, val, valsno, maxvallen, - maxdatalen); - // malloc buffers? - if (MAX_REG_VAL_NAME < maxvallen) - BUG("value name buffer too small (%d), needed: %dB.", - MAX_REG_VAL_NAME, maxvallen); - if (MAX_REG_DATA_SIZE < maxdatalen) - BUG("value data buffer too small (%d), needed: %dB.", - MAX_REG_DATA_SIZE, maxdatalen); - /* connection could still succeeded, so carry on */ - } - - for (i = 0, j = 0; i < valsno; i ++) { - vallen = sizeof(val) / sizeof(val[0]); - datalen = MAX_REG_DATA_SIZE; - d = (BYTE *)&buff[j * datalen]; - if (RegEnumValue(hkey, i, val, &vallen, /*reserved*/NULL, &valtype, - d, &datalen) != ERROR_SUCCESS) { - ERR("failed to read register subkey value."); - goto end; - } - if (valtype != REG_SZ) { - INFO("unused register values of type %d -- skipping.", - valtype); - continue; - } - tval = (tstr_st) { - val, vallen - }; - tdata = (tstr_st) { - (SQLTCHAR *)d, datalen - }; - if (assign_config_attr(attrs, &tval, &tdata, FALSE)) { - j ++; - DBG("reg entry`" LTPDL "`: `" LTPDL "` assigned.", - LTSTR(&tval), LTSTR(&tdata)); - } else { - INFO("ignoring reg entry `" LTPDL "`: `" LTPDL "`.", - LTSTR(&tval), LTSTR(&tdata)); - /* entry not directly relevant to driver config */ - } - } - - - ret = TRUE; -end: - RegCloseKey(hkey); - - return ret; -} -#else /* defined(_WIN32) || defined (WIN32) */ -#error "unsupported platform" /* TODO */ -#endif /* defined(_WIN32) || defined (WIN32) */ - -static BOOL prompt_user(config_attrs_st *attrs, BOOL disable_conn) -{ - // - // TODO: display settings dialog box; - // An error message (if popping it more than once) might be needed. - // - return FALSE; -} - SQLRETURN EsSQLDriverConnectW ( SQLHDBC hdbc, @@ -1873,14 +1324,14 @@ SQLRETURN EsSQLDriverConnectW { esodbc_dbc_st *dbc = DBCH(hdbc); SQLRETURN ret; - config_attrs_st attrs; - wstr_st orig_dsn; - BOOL disable_conn = FALSE; - BOOL user_canceled = FALSE; - TCHAR buff[(sizeof(config_attrs_st)/sizeof(wstr_st)) * MAX_REG_DATA_SIZE]; - + esodbc_dsn_attrs_st attrs; + SQLWCHAR buff_dsn[ESODBC_DSN_MAX_ATTR_LEN]; + wstr_st orig_dsn = {buff_dsn, 0}; + BOOL disable_nonconn = FALSE; + BOOL prompt_user = TRUE; + int res; - memset(&attrs, 0, sizeof(attrs)); + init_dsn_attrs(&attrs); if (szConnStrIn) { DBGH(dbc, "Input connection string: '"LWPD"'[%d].", szConnStrIn, @@ -1893,7 +1344,9 @@ SQLRETURN EsSQLDriverConnectW RET_HDIAGS(dbc, SQL_STATE_HY000); } /* original received DSN saved for later query by the app */ - orig_dsn = attrs.dsn; + memcpy(orig_dsn.str, attrs.dsn.str, + attrs.dsn.cnt * sizeof(*attrs.dsn.str)); + orig_dsn.cnt = attrs.dsn.cnt; /* set DSN (to DEFAULT) only if both DSN and Driver kw are missing */ if ((! attrs.driver.cnt) && (! attrs.dsn.cnt)) { @@ -1903,11 +1356,15 @@ SQLRETURN EsSQLDriverConnectW * Default data source. */ INFOH(dbc, "no DRIVER or DSN keyword found in connection string: " "using the \"DEFAULT\" DSN."); - attrs.dsn = MK_WSTR("DEFAULT"); + res = assign_dsn_attr(&attrs, &MK_WSTR(ESODBC_DSN_DSN), + &MK_WSTR("DEFAULT"), /*overwrite?*/TRUE); + assert(0 < res); } } else { INFOH(dbc, "empty connection string: using the \"DEFAULT\" DSN."); - attrs.dsn = MK_WSTR("DEFAULT"); + res = assign_dsn_attr(&attrs, &MK_WSTR(ESODBC_DSN_DSN), + &MK_WSTR("DEFAULT"), /*overwrite?*/TRUE); + assert(0 < res); } assert(attrs.driver.cnt || attrs.dsn.cnt); @@ -1919,14 +1376,16 @@ SQLRETURN EsSQLDriverConnectW * the information in the connection string." */ INFOH(dbc, "configuring the driver by DSN '" LWPDL "'.", LWSTR(&attrs.dsn)); - if (! read_system_info(&attrs, buff)) { + if (! read_system_info(&attrs)) { /* warn, but try to carry on */ WARNH(dbc, "failed to read system info for DSN '" LWPDL "' data.", LWSTR(&attrs.dsn)); /* DM should take care of this, but just in case */ if (! EQ_WSTR(&attrs.dsn, &MK_WSTR("DEFAULT"))) { - attrs.dsn = MK_WSTR("DEFAULT"); - if (! read_system_info(&attrs, buff)) { + res = assign_dsn_attr(&attrs, &MK_WSTR(ESODBC_DSN_DSN), + &MK_WSTR("DEFAULT"), /*overwrite?*/TRUE); + assert(0 < res); + if (! read_system_info(&attrs)) { ERRH(dbc, "failed to read system info for default DSN."); RET_HDIAGS(dbc, SQL_STATE_IM002); } @@ -1936,11 +1395,12 @@ SQLRETURN EsSQLDriverConnectW /* "If the connection string contains the DRIVER keyword, the driver * cannot retrieve information about the data source from the system * information." */ - INFOH(dbc, "configuring the driver '" LWPDL "'.", LWSTR(&attrs.driver)); + INFOH(dbc, "configuring the driver '" LWPDL "'.", + LWSTR(&attrs.driver)); } /* whatever attributes haven't yet been set, init them with defaults */ - assign_defaults(&attrs); + assign_dsn_defaults(&attrs); switch (fDriverCompletion) { case SQL_DRIVER_NOPROMPT: @@ -1951,27 +1411,25 @@ SQLRETURN EsSQLDriverConnectW break; case SQL_DRIVER_COMPLETE_REQUIRED: - disable_conn = TRUE; - //no break; + disable_nonconn = TRUE; + /* no break */ case SQL_DRIVER_COMPLETE: /* try connect first, then, if that fails, prompt user */ - while (! SQL_SUCCEEDED(do_connect(dbc, &attrs))) { - if (! prompt_user(&attrs, disable_conn)) - /* user canceled */ - { - return SQL_NO_DATA; - } - } - break; - - case SQL_DRIVER_PROMPT: + prompt_user = FALSE; + /* no break; */ + case SQL_DRIVER_PROMPT: /* prompt user first, then try connect */ do { - /* prompt user first, then try connect */ - if (! prompt_user(&attrs, FALSE)) + res = prompt_user ? + prompt_user_config(hdbc, &attrs, FALSE) : 1; + if (res < 0) { + ERRH(dbc, "user interaction failed."); + RET_HDIAGS(dbc, SQL_STATE_IM008); + } else if (! res) { /* user canceled */ - { return SQL_NO_DATA; } + /* promt user on next iteration */ + prompt_user = TRUE; } while (! SQL_SUCCEEDED(do_connect(dbc, &attrs))); break; @@ -2008,7 +1466,9 @@ SQLRETURN EsSQLDriverConnectW /* return the final connection string */ if (szConnStrOut || pcchConnStrOut) { /* might have been reset to DEFAULT, if orig was not found */ - attrs.dsn = orig_dsn; + res = assign_dsn_attr(&attrs, &MK_WSTR(ESODBC_DSN_DSN), &orig_dsn, + /*overwrite?*/TRUE); + assert(0 < res); if (! write_connection_string(&attrs, szConnStrOut, cchConnStrOutMax, pcchConnStrOut)) { ERRH(dbc, "failed to build output connection string."); diff --git a/driver/defs.h b/driver/defs.h index e6ea67e8..e8d85416 100644 --- a/driver/defs.h +++ b/driver/defs.h @@ -100,9 +100,11 @@ */ /* maximum URL size */ #define ESODBC_MAX_URL_LEN 2048 -/* maximum DNS name */ -/* SQL_MAX_DSN_LENGTH=32 < IPv6 len */ -#define ESODBC_MAX_DNS_LEN 255 +/* maximum DNS attribute value lenght (should be long enought to accomodate a + * decently long FQ file path name) */ +#define ESODBC_DSN_MAX_ATTR_LEN 1024 +/* sample DSN name provisioned with the installation */ +#define ESODBC_DSN_SAMPLE_NAME "Elasticsearch ODBC Sample DSN" /* SQL plugin's REST endpoint for SQL */ #define ELASTIC_SQL_PATH "/_xpack/sql" @@ -144,7 +146,6 @@ /* default tracing level */ #define ESODBC_DEF_TRACE_LEVEL "WARN" - /* * * Driver/Elasticsearch capabilities diff --git a/driver/dsn.c b/driver/dsn.c new file mode 100644 index 00000000..cfc4c281 --- /dev/null +++ b/driver/dsn.c @@ -0,0 +1,855 @@ +/* + * 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. + */ + +#include "util.h" /* includes windows.h, needed for odbcinst.h */ +#include + +#include "dsn.h" +#include "log.h" + +#define ODBC_REG_SUBKEY_PATH "SOFTWARE\\ODBC\\ODBC.INI" +#define REG_HKLM "HKEY_LOCAL_MACHINE" +#define REG_HKCU "HKEY_CURRENT_USER" + + +void init_dsn_attrs(esodbc_dsn_attrs_st *attrs) +{ + size_t i; + wstr_st *wstr; + + memset(attrs, 0, sizeof(*attrs)); + + for (i = 0; i < ESODBC_DSN_ATTRS_COUNT; i ++) { + wstr = &((wstr_st *)attrs)[i]; + wstr->str = &attrs->buff[i * ESODBC_DSN_MAX_ATTR_LEN]; + } +} + +#define DSN_NOT_MATCHED 0 +#define DSN_NOT_OVERWRITTEN 1 +#define DSN_ASSIGNED 2 +#define DSN_OVERWRITTEN 3 +/* + * returns: + * positive on keyword match: + * DSN_ASSIGNED for assignment on blank, + * DSN_OVERWRITTEN for assignment over value, + * DSN_NOT_OVERWRITTEN for skipped assignment due to !overwrite; + * 0 on keyword mismatch; + * negative on error; + */ +int assign_dsn_attr(esodbc_dsn_attrs_st *attrs, + wstr_st *keyword, wstr_st *value, BOOL overwrite) +{ + struct { + wstr_st *kw; + wstr_st *val; + } *iter, map[] = { + {&MK_WSTR(ESODBC_DSN_DRIVER), &attrs->driver}, + {&MK_WSTR(ESODBC_DSN_DESCRIPTION), &attrs->description}, + {&MK_WSTR(ESODBC_DSN_DSN), &attrs->dsn}, + {&MK_WSTR(ESODBC_DSN_PWD), &attrs->pwd}, + {&MK_WSTR(ESODBC_DSN_UID), &attrs->uid}, + {&MK_WSTR(ESODBC_DSN_SAVEFILE), &attrs->savefile}, + {&MK_WSTR(ESODBC_DSN_FILEDSN), &attrs->filedsn}, + {&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_TIMEOUT), &attrs->timeout}, + {&MK_WSTR(ESODBC_DSN_FOLLOW), &attrs->follow}, + {&MK_WSTR(ESODBC_DSN_CATALOG), &attrs->catalog}, + {&MK_WSTR(ESODBC_DSN_PACKING), &attrs->packing}, + {&MK_WSTR(ESODBC_DSN_MAX_FETCH_SIZE), &attrs->max_fetch_size}, + {&MK_WSTR(ESODBC_DSN_MAX_BODY_SIZE_MB), &attrs->max_body_size}, + {&MK_WSTR(ESODBC_DSN_TRACE_FILE), &attrs->trace_file}, + {&MK_WSTR(ESODBC_DSN_TRACE_LEVEL), &attrs->trace_level}, + {NULL, NULL} + }; + int ret; + + if (ESODBC_DSN_MAX_ATTR_LEN < value->cnt) { + ERR("attribute value lenght too large: %zu; max=%zu.", value->cnt, + ESODBC_DSN_MAX_ATTR_LEN); + return -1; + } + + for (iter = &map[0]; iter->kw; iter ++) { + if (! EQ_CASE_WSTR(iter->kw, keyword)) { + continue; + } + /* it's a match: has it been assigned already? */ + if (iter->val->cnt) { + if (! overwrite) { + INFO("keyword '" LWPDL "' already assigned; " + "ignoring new `" LWPDL "`, keeping previous `" LWPDL "`.", + LWSTR(iter->kw), LWSTR(value), LWSTR(iter->val)); + return DSN_NOT_OVERWRITTEN; + } + INFO("keyword '" LWPDL "' already assigned: " + "overwriting previous `" LWPDL "` with new `" LWPDL "`.", + LWSTR(iter->kw), LWSTR(iter->val), LWSTR(value)); + ret = DSN_OVERWRITTEN; + } else { + INFO("keyword '" LWPDL "' new assignment: `" LWPDL "`.", + LWSTR(iter->kw), LWSTR(value)); + ret = DSN_ASSIGNED; + } + memcpy(iter->val->str, value->str, value->cnt * sizeof(*value->str)); + iter->val->cnt = value->cnt; + return ret; + } + + /* entry not directly relevant to driver config */ + WARN("keyword `" LWPDL "` (with value `" LWPDL "`) not DSN config " + "specific, so not assigned.", LWSTR(keyword), LWSTR(value)); + return DSN_NOT_MATCHED; +} + +/* + * Advance position in string, skipping white space. + * if exended is true, `;` will be treated as white space too. + */ +static SQLWCHAR *skip_ws(SQLWCHAR **pos, SQLWCHAR *end, BOOL extended) +{ + while (*pos < end) { + switch(**pos) { + case ' ': + case '\t': + case '\r': + case '\n': + (*pos)++; + break; + + case '\0': + return NULL; + + case ';': + if (extended) { + (*pos)++; + break; + } + // no break; + + default: + return *pos; + } + } + + /* end of string reached */ + return NULL; +} + +/* + * Parse a keyword or a value. + * Within braces, any character is allowed, safe for \0 (i.e. no "bla{bla\0" + * is supported as keyword or value). + * Braces within braces are allowed. + */ +static BOOL parse_token(BOOL is_value, SQLWCHAR **pos, SQLWCHAR *end, + wstr_st *token) +{ + BOOL brace_escaped = FALSE; + int open_braces = 0; + SQLWCHAR *start = *pos; + BOOL stop = FALSE; + + while (*pos < end && (! stop)) { + switch (**pos) { + case '\\': + if (! is_value) { + ERR("keywords and data source names cannot contain " + "the backslash."); + return FALSE; + } + (*pos)++; + break; + + case ' ': + case '\t': + case '\r': + case '\n': + if (open_braces || is_value) { + (*pos)++; + } else { + stop = TRUE; + } + break; + + case '=': + if (open_braces || is_value) { + (*pos)++; + } else { + stop = TRUE; + } + break; + + case ';': + if (open_braces) { + (*pos)++; + } else if (is_value) { + stop = TRUE; + } else { + ERR("';' found while parsing keyword"); + return FALSE; + } + break; + + case '\0': + if (open_braces) { + ERR("null terminator found while within braces"); + return FALSE; + } else if (! is_value) { + ERR("null terminator found while parsing keyword."); + return FALSE; + } /* else: \0 used as delimiter of value string */ + stop = TRUE; + break; + + case '{': + if (*pos == start) { + open_braces ++; + } else if (open_braces) { + ERR("token started with opening brace, so can't use " + "inner braces"); + /* b/c: `{foo{ = }}bar} = val`; `foo{bar}baz` is fine */ + return FALSE; + } + (*pos)++; + break; + + case '}': + if (open_braces) { + open_braces --; + brace_escaped = TRUE; + stop = TRUE; + } + (*pos)++; + break; + + default: + (*pos)++; + } + } + + if (open_braces) { + ERR("string finished with open braces."); + return FALSE; + } + + token->str = start + (brace_escaped ? 1 : 0); + token->cnt = (*pos - start) - (brace_escaped ? 2 : 0); + return TRUE; +} + +static SQLWCHAR *parse_separator(SQLWCHAR **pos, SQLWCHAR *end) +{ + if (*pos < end) { + if (**pos == '=') { + (*pos)++; + return *pos; + } + } + return NULL; +} + +/* + * - "keywords and attribute values that contain the characters []{}(),;?*=!@ + * not enclosed with braces should be avoided"; => allowed. + * - "value of the DSN keyword cannot consist only of blanks and should not + * contain leading blanks"; + * - "keywords and data source names cannot contain the backslash (\) + * character."; + * - "value enclosed with braces ({}) containing any of the characters + * []{}(),;?*=!@ is passed intact to the driver."; + * + * foo{bar}=baz=foo; => "foo{bar}" = "baz=foo" + * + * * `=` is delimiter, unless within {} + * * `{` and `}` allowed within {} + * * brances need to be returned to out-str; + */ +BOOL parse_connection_string(esodbc_dsn_attrs_st *attrs, + SQLWCHAR *szConnStrIn, SQLSMALLINT cchConnStrIn) +{ + + SQLWCHAR *pos; + SQLWCHAR *end; + wstr_st keyword, value; + + /* parse and assign attributes in connection string */ + pos = szConnStrIn; + end = pos + (cchConnStrIn == SQL_NTS ? SHRT_MAX : cchConnStrIn); + + while (skip_ws(&pos, end, TRUE)) { + if (! parse_token(FALSE, &pos, end, &keyword)) { + ERR("failed to parse keyword at position %zd", + pos - szConnStrIn); + return FALSE; + } + + if (! skip_ws(&pos, end, FALSE)) { + return FALSE; + } + + if (! parse_separator(&pos, end)) { + ERR("failed to parse separator (`=`) at position %zd", + pos - szConnStrIn); + return FALSE; + } + + if (! skip_ws(&pos, end, FALSE)) { + return FALSE; + } + + if (! parse_token(TRUE, &pos, end, &value)) { + ERR("failed to parse value at position %zd", + pos - szConnStrIn); + return FALSE; + } + + DBG("read connection string attribute: `" LWPDL "` = `" LWPDL "`.", + LWSTR(&keyword), LWSTR(&value)); + if (assign_dsn_attr(attrs, &keyword, &value, TRUE) < 0) { + ERRN("failed to assign keyword `" LWPDL "` with val `" LWPDL "`.", + LWSTR(&keyword), LWSTR(&value)); + return FALSE; + } + } + + return TRUE; +} + +BOOL parse_00_list(esodbc_dsn_attrs_st *attrs, SQLWCHAR *list00) +{ + SQLWCHAR *pos; + size_t cnt; + + for (pos = (SQLWCHAR *)list00; *pos; pos += cnt + 1) { + cnt = wcslen(pos); + + if (SHRT_MAX < cnt) { + ERR("invalid list lenght (%zu).", cnt); + return FALSE; + } + if (! parse_connection_string(attrs, pos, (SQLSMALLINT)cnt)) { + ERR("failed parsing list entry `" LWPDL "`.", cnt, pos); + return FALSE; + } + } + return TRUE; +} + +static void log_installer_err() +{ + DWORD ecode; + SQLWCHAR buff[SQL_MAX_MESSAGE_LENGTH]; + WORD msg_len; + int i = 0; + + while (SQL_SUCCEEDED(SQLInstallerError(++ i, &ecode, buff, + sizeof(buff)/sizeof(buff[0]), &msg_len))) { + ERR("#%i: errcode=%d: " LWPDL ".", i, ecode, msg_len, buff); + } +} + +/* + * Checks if a DSN entry with the given name exists already. + * Returns: + * . negative on failure + * . 0 on false + * . positive on true. + */ +int system_dsn_exists(wstr_st *dsn) +{ + int res; + SQLWCHAR kbuff[ESODBC_DSN_MAX_ATTR_LEN]; + + /* '\' can't be a key name */ + res = SQLGetPrivateProfileStringW(dsn->str, NULL, MK_WPTR("\\"), + kbuff, sizeof(kbuff)/sizeof(kbuff[0]), MK_WPTR(SUBKEY_ODBC)); + if (res < 0) { + ERR("failed to query for DSN registry keys."); + log_installer_err(); + return -1; + } + /* subkey can be found, but have nothing beneath => res == 0 */ + return (! res) || (kbuff[0] != MK_WPTR('\\')); + +} +/* + * Reads the system entries for a DSN given into a doubly null-terminated + * attributes list. + * + * The defaults are always filled in. + * + * The list - as received by ConfigDSN() - seems to only contain the 'DSN' + * keyword, though the documentation mentions a full list. However, if a full + * is provided, the values are going to be taken into account, but possibly + * overwritten by registry entries (which theoretically should be the same + * anyways). + */ +BOOL load_system_dsn(esodbc_dsn_attrs_st *attrs, SQLWCHAR *list00) +{ + int res; + SQLWCHAR buff[ESODBC_DSN_MAX_ATTR_LEN], *pos; + SQLWCHAR kbuff[sizeof(attrs->buff)/sizeof(attrs->buff[0])]; + wstr_st keyword, value; + + if (! parse_00_list(attrs, list00)) { + ERR("failed to parse doubly null-terminated attributes " + "list `" LWPD "`.", list00); + return FALSE; + } + /* ConfigDSN() requirement */ + if (attrs->driver.cnt) { + ERR("function can not accept '" ESODBC_DSN_DRIVER "' keyword."); + return FALSE; + } + + if (attrs->dsn.cnt) { + DBG("loading attributes for DSN `" LWPDL "`.", LWSTR(&attrs->dsn)); + + /* load available key *names* first; + * it's another doubly null-terminated list (w/o values). */ + res = SQLGetPrivateProfileStringW(attrs->dsn.str, NULL, MK_WPTR(""), + kbuff, sizeof(kbuff)/sizeof(kbuff[0]), MK_WPTR(SUBKEY_ODBC)); + if (res < 0) { + ERR("failed to query for DSN registry keys."); + log_installer_err(); + return FALSE; + } + /* for each key name, read its value and add it to 'attrs' */ + for (pos = kbuff; *pos; pos += keyword.cnt + 1) { + keyword.str = pos; + keyword.cnt = wcslen(pos); + + if (EQ_CASE_WSTR(&keyword, &MK_WSTR(ESODBC_DSN_DRIVER))) { + /* skip the 'Driver' keyword */ + continue; + } + + res = SQLGetPrivateProfileStringW(attrs->dsn.str, pos, MK_WPTR(""), + buff, sizeof(buff)/sizeof(buff[0]), MK_WPTR(SUBKEY_ODBC)); + if (res < 0) { + ERR("failed to query value for DSN registry key `" LWPDL "`.", + LWSTR(&keyword)); + log_installer_err(); + return FALSE; + } else { + value.cnt = (size_t)res; + value.str = buff; + } + /* assign it to the config */ + DBG("read DSN attribute: `" LWPDL "` = `" LWPDL "`.", + LWSTR(&keyword), LWSTR(&value)); + /* assign attributes not yet given in the 00-list */ + if (assign_dsn_attr(attrs, &keyword, &value, /*over?*/FALSE) < 0) { + ERR("keyword '" LWPDL "' couldn't be assigned.", + LWSTR(&keyword)); + return FALSE; + } + } + } + + if (! assign_dsn_defaults(attrs)) { + ERR("OOM assigning defaults"); + return FALSE; + } + + return TRUE; +} + +BOOL write_system_dsn(esodbc_dsn_attrs_st *attrs, BOOL create_new) +{ + struct { + wstr_st *kw; + wstr_st *val; + } *iter, map[] = { + /* Driver */ + {&MK_WSTR(ESODBC_DSN_DESCRIPTION), &attrs->description}, + /* DSN */ + {&MK_WSTR(ESODBC_DSN_PWD), &attrs->pwd}, + {&MK_WSTR(ESODBC_DSN_UID), &attrs->uid}, + /* SAVEILE */ + /* FILEDSN */ + {&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_TIMEOUT), &attrs->timeout}, + {&MK_WSTR(ESODBC_DSN_FOLLOW), &attrs->follow}, + {&MK_WSTR(ESODBC_DSN_CATALOG), &attrs->catalog}, + {&MK_WSTR(ESODBC_DSN_PACKING), &attrs->packing}, + {&MK_WSTR(ESODBC_DSN_MAX_FETCH_SIZE), &attrs->max_fetch_size}, + {&MK_WSTR(ESODBC_DSN_MAX_BODY_SIZE_MB), &attrs->max_body_size}, + {&MK_WSTR(ESODBC_DSN_TRACE_FILE), &attrs->trace_file}, + {&MK_WSTR(ESODBC_DSN_TRACE_LEVEL), &attrs->trace_level}, + {NULL, NULL} + }; + + if (create_new) { + if (! SQLValidDSNW(attrs->dsn.str)) { + ERR("invalid DSN value `" LWPDL "`.", LWSTR(&attrs->dsn)); + return FALSE; + } + INFO("creating new DSN `" LWPDL "` for driver ` " LWPDL " `.", + LWSTR(&attrs->dsn), LWSTR(&attrs->driver)); + if (! SQLWriteDSNToIniW(attrs->dsn.str, attrs->driver.str)) { + ERR("failed to add DSN `" LWPDL "` for driver ` " LWPDL " ` to " + ".INI.", LWSTR(&attrs->dsn), LWSTR(&attrs->driver)); + return FALSE; + } + } + + for (iter = &map[0]; iter->kw; iter ++) { + if (! iter->val->cnt) { + DBG("value `" LWPDL "` not provisioned.", LWSTR(iter->kw)); + continue; + } + if (! SQLWritePrivateProfileStringW(attrs->dsn.str, + iter->kw->str, iter->val->str, MK_WPTR(SUBKEY_ODBC))) { + ERR("failed to write key `" LWPDL "` with value `" LWPDL "`.", + LWSTR(iter->kw), LWSTR(iter->val)); + return FALSE; + } + DBG("key `" LWPDL "` with value `" LWPDL "` written to " SUBKEY_ODBC + ".", LWSTR(iter->kw), LWSTR(iter->val)); + } + return TRUE; +} + +static inline BOOL needs_braces(wstr_st *token) +{ + int i; + + for (i = 0; i < token->cnt; i ++) { + switch(token->str[i]) { + case ' ': + case '\t': + case '\r': + case '\n': + case '=': + case ';': + return TRUE; + } + } + return FALSE; +} + +/* build a connection string to be written in the DSN files */ +BOOL write_connection_string(esodbc_dsn_attrs_st *attrs, + SQLWCHAR *szConnStrOut, SQLSMALLINT cchConnStrOutMax, + SQLSMALLINT *pcchConnStrOut) +{ + int n, braces; + size_t pos; + wchar_t *format; + struct { + wstr_st *val; + char *kw; + } *iter, map[] = { + {&attrs->driver, ESODBC_DSN_DRIVER}, + /* Description */ + {&attrs->dsn, ESODBC_DSN_DSN}, + {&attrs->pwd, ESODBC_DSN_PWD}, + {&attrs->uid, ESODBC_DSN_UID}, + {&attrs->savefile, ESODBC_DSN_SAVEFILE}, + {&attrs->filedsn, ESODBC_DSN_FILEDSN}, + {&attrs->server, ESODBC_DSN_SERVER}, + {&attrs->port, ESODBC_DSN_PORT}, + {&attrs->secure, ESODBC_DSN_SECURE}, + {&attrs->timeout, ESODBC_DSN_TIMEOUT}, + {&attrs->follow, ESODBC_DSN_FOLLOW}, + {&attrs->catalog, ESODBC_DSN_CATALOG}, + {&attrs->packing, ESODBC_DSN_PACKING}, + {&attrs->max_fetch_size, ESODBC_DSN_MAX_FETCH_SIZE}, + {&attrs->max_body_size, ESODBC_DSN_MAX_BODY_SIZE_MB}, + {&attrs->trace_file, ESODBC_DSN_TRACE_FILE}, + {&attrs->trace_level, ESODBC_DSN_TRACE_LEVEL}, + {NULL, NULL} + }; + + for (iter = &map[0], pos = 0; iter->val; iter ++) { + if (iter->val->cnt) { + braces = needs_braces(iter->val) ? 2 : 0; + if (cchConnStrOutMax && szConnStrOut) { + /* swprintf will fail if formated string would overrun the + * buffer size */ + if (cchConnStrOutMax - pos < iter->val->cnt + braces) { + /* indicate that we've reached buffer limits: only account + * for how long the string would be */ + cchConnStrOutMax = 0; + pos += iter->val->cnt + braces; + continue; + } + if (braces) { + format = WPFCP_DESC "={" WPFWP_LDESC "};"; + } else { + format = WPFCP_DESC "=" WPFWP_LDESC ";"; + } + n = swprintf(szConnStrOut + pos, cchConnStrOutMax - pos, + format, iter->kw, LWSTR(iter->val)); + if (n < 0) { + ERRN("failed to outprint connection string (space " + "left: %d; needed: %d).", cchConnStrOutMax - pos, + iter->val->cnt); + return FALSE; + } else { + pos += n; + } + } else { + /* simply increment the counter, since the untruncated length + * need to be returned to the app */ + pos += iter->val->cnt + braces; + } + } + } + + *pcchConnStrOut = (SQLSMALLINT)pos; + + DBG("Output connection string: `" LWPD "`; out len: %d.", + szConnStrOut, pos); + return TRUE; +} + +BOOL assign_dsn_defaults(esodbc_dsn_attrs_st *attrs) +{ + /* assign defaults where not assigned and applicable */ + if (assign_dsn_attr(attrs, + &MK_WSTR(ESODBC_DSN_SERVER), &MK_WSTR(ESODBC_DEF_SERVER), + /*overwrite?*/FALSE) < 0) { + return FALSE; + } + if (assign_dsn_attr(attrs, + &MK_WSTR(ESODBC_DSN_PORT), &MK_WSTR(ESODBC_DEF_PORT), + /*overwrite?*/FALSE) < 0) { + return FALSE; + } + if (assign_dsn_attr(attrs, + &MK_WSTR(ESODBC_DSN_SECURE), &MK_WSTR(ESODBC_DEF_SECURE), + /*overwrite?*/FALSE) < 0) { + return FALSE; + } + if (assign_dsn_attr(attrs, + &MK_WSTR(ESODBC_DSN_TIMEOUT), &MK_WSTR(ESODBC_DEF_TIMEOUT), + /*overwrite?*/FALSE) < 0) { + return FALSE; + } + if (assign_dsn_attr(attrs, + &MK_WSTR(ESODBC_DSN_FOLLOW), &MK_WSTR(ESODBC_DEF_FOLLOW), + /*overwrite?*/FALSE) < 0) { + return FALSE; + } + + if (assign_dsn_attr(attrs, + &MK_WSTR(ESODBC_DSN_PACKING), &MK_WSTR(ESODBC_DEF_PACKING), + /*overwrite?*/FALSE) < 0) { + return FALSE; + } + if (assign_dsn_attr(attrs, + &MK_WSTR(ESODBC_DSN_MAX_FETCH_SIZE), + &MK_WSTR(ESODBC_DEF_FETCH_SIZE), + /*overwrite?*/FALSE) < 0) { + return FALSE; + } + if (assign_dsn_attr(attrs, &MK_WSTR(ESODBC_DSN_MAX_BODY_SIZE_MB), + &MK_WSTR(ESODBC_DEF_MAX_BODY_SIZE_MB), + /*overwrite?*/FALSE) < 0) { + return FALSE; + } + + /* default: no trace file */ + if (assign_dsn_attr(attrs, + &MK_WSTR(ESODBC_DSN_TRACE_LEVEL), &MK_WSTR(ESODBC_DEF_TRACE_LEVEL), + /*overwrite?*/FALSE) < 0) { + return FALSE; + } + + return TRUE; +} + + +#if defined(_WIN32) || defined (WIN32) +/* + * Reads system registry for ODBC DSN subkey named in attrs->dsn. + * TODO: use odbccp32.dll's SQLGetPrivateProfileString() & co. instead of + * direct registry access: + * https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlgetprivateprofilestring-function + */ +BOOL read_system_info(esodbc_dsn_attrs_st *attrs) +{ + HKEY hkey; + BOOL ret = FALSE; + const char *ktree; + DWORD valsno/* number of values in subkey */, i, res; + DWORD maxvallen; /* len of longest value name */ + DWORD maxdatalen; /* len of longest data (buffer) */ + DWORD vallen; + DWORD valtype; + DWORD datalen; + SQLWCHAR val_buff[ESODBC_DSN_MAX_ATTR_LEN]; + wstr_st val_name = {val_buff, 0}; + SQLWCHAR data_buff[ESODBC_DSN_MAX_ATTR_LEN]; + wstr_st val_data = {data_buff, 0}; + + if (swprintf(val_buff, sizeof(val_buff)/sizeof(val_buff[0]), + WPFCP_DESC "\\" WPFWP_LDESC, + ODBC_REG_SUBKEY_PATH, LWSTR(&attrs->dsn)) < 0) { + ERRN("failed to print registry key path."); + return FALSE; + } + /* try accessing local user's config first, if that fails, systems' */ + if (RegOpenKeyExW(HKEY_CURRENT_USER, val_buff, /*options*/0, KEY_READ, + &hkey) != ERROR_SUCCESS) { + INFO("failed to open registry key `" REG_HKCU "\\" LWPD "`.", + val_buff); + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, val_buff, /*options*/0, KEY_READ, + &hkey) != ERROR_SUCCESS) { + INFO("failed to open registry key `" REG_HKLM "\\" LWPD "`.", + val_buff); + goto end; + } else { + ktree = REG_HKLM; + } + } else { + ktree = REG_HKCU; + } + + if (RegQueryInfoKeyW(hkey, /*key class*/NULL, /*len of key class*/NULL, + /*reserved*/NULL, /*no subkeys*/NULL, /*longest subkey*/NULL, + /*longest subkey class name*/NULL, &valsno, &maxvallen, + &maxdatalen, /*sec descr*/NULL, + /*update time */NULL) != ERROR_SUCCESS) { + ERRN("Failed to query registery key info for path `%s\\" LWPD + "`.", ktree, val_buff); + goto end; + } else { + DBG("Subkey '%s\\" LWPD "': vals: %d, lengthiest name: %d, " + "lengthiest data: %d.", ktree, val_buff, valsno, maxvallen, + maxdatalen); + if (sizeof(val_buff)/sizeof(val_buff[0]) < maxvallen) { + WARN("value name buffer too small (%d), needed: %dB.", + sizeof(val_buff)/sizeof(val_buff[0]), maxvallen); + } + if (sizeof(data_buff)/sizeof(data_buff[0]) < maxdatalen) { + WARN("value data buffer too small (%d), needed: %dB.", + sizeof(data_buff)/sizeof(data_buff[0]), maxdatalen); + } + /* the registry might contain other, non connection-related strings, + * so these conditions are not necearily an error. */ + } + + for (i = 0; i < valsno; i ++) { + vallen = sizeof(val_buff) / sizeof(val_buff[0]); + datalen = sizeof(data_buff); + + if (RegEnumValueW(hkey, i, val_buff, &vallen, /*reserved*/NULL, &valtype, + (BYTE *)data_buff, &datalen) != ERROR_SUCCESS) { + ERR("failed to read register subkey value."); + goto end; + } + /* vallen doesn't count the \0 */ + val_name.cnt = vallen; + if (valtype != REG_SZ) { + INFO("skipping register value `" LWPDL "` of type %d.", + LWSTR(&val_name), valtype); + continue; + } + if (datalen <= 0) { + INFO("skipping value `" LWPDL "` with empty data.", + LWSTR(&val_name)); + continue; + } + assert(datalen % sizeof(SQLWCHAR) == 0); + /* datalen counts all bytes returned, so including the \0 */ + val_data.cnt = datalen/sizeof(SQLWCHAR) - /*\0*/1; + + if ((res = assign_dsn_attr(attrs, &val_name, &val_data, + /*overwrite?*/FALSE)) < 0) { + ERR("failed to assign reg entry `" LTPDL "`: `" LTPDL "`.", + LTSTR(&val_name), LTSTR(&val_data)); + goto end; + } else if (res == DSN_ASSIGNED) { + DBG("reg entry`" LTPDL "`: `" LTPDL "` assigned.", + LTSTR(&val_name), LTSTR(&val_data)); + } + /* if == 0: entry not directly relevant to driver config + * if == DSN_NOT_OVERWRITTEN: entry provisioned in the conn str. */ + } + + ret = TRUE; +end: + RegCloseKey(hkey); + + return ret; +} +#else /* defined(_WIN32) || defined (WIN32) */ +#error "unsupported platform" /* TODO */ +#endif /* defined(_WIN32) || defined (WIN32) */ + + +// asks user for the config data +// returns: +// . negative: on failure +// . 0: user canceled +// . positive: user provided input +int prompt_user_config(HWND hwndParent, esodbc_dsn_attrs_st *attrs, + /* disable non-connect-related controls? */ + BOOL disable_nonconn) +{ + static int attempts = 0; + + if (! hwndParent) { + INFO("no window handler provided -- configuration skipped."); + return 1; + } + TRACE; + + // + // TODO: problematic: called from setup (dyn) and driverconnect (static), + // so dunno when to duplicate: either use all static, all dyn, add + // attrs.dynamic? + // + + if (assign_dsn_attr(attrs, &MK_WSTR(ESODBC_DSN_DSN), + &MK_WSTR("My Elasticsearch ODBC DSN"), FALSE) <= 0) { + //&MK_WSTR("My Elasticsearch ODBC DSN"), TRUE, TRUE) <= 0) { + return -1; + } +#if 1 + if (assign_dsn_attr(attrs, &MK_WSTR(ESODBC_DSN_TRACE_LEVEL), + &MK_WSTR("INFO"), FALSE) <= 0) { + return -1; + } +#endif + if (1 < attempts ++) { + /* prevent infinite loops */ + return 0; + } + + if (SQL_MAX_DSN_LENGTH < attrs->dsn.cnt) { + ERR("DSN name longer than max (%d).", SQL_MAX_DSN_LENGTH); + } + + return 1; +} + +// asks user if we should overwrite the existing DSN +// returns: +// . negative on failure +// . 0 on false +// . positive on true +int prompt_user_overwrite(HWND hwndParent, wstr_st *dsn) +{ + if (! hwndParent) { + INFO("no window handler provided -- forcing overwrite."); + return 1; + } + TRACE; + return 1; +} + + +/* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : */ diff --git a/driver/dsn.h b/driver/dsn.h new file mode 100644 index 00000000..d7b0fe56 --- /dev/null +++ b/driver/dsn.h @@ -0,0 +1,83 @@ +/* + * 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. + */ + +#ifndef __DSN_H__ +#define __DSN_H__ + +#include "util.h" +#include "defs.h" + +#define SUBKEY_ODBCINST "ODBCINST.INI" +#define SUBKEY_ODBC "ODBC.INI" + +/* attribute keywords used in connection strings */ +#define ESODBC_DSN_DRIVER "Driver" +#define ESODBC_DSN_DESCRIPTION "Description" +#define ESODBC_DSN_DSN "DSN" +#define ESODBC_DSN_PWD "PWD" +#define ESODBC_DSN_UID "UID" +#define ESODBC_DSN_SAVEFILE "SAVEFILE" +#define ESODBC_DSN_FILEDSN "FILEDSN" +#define ESODBC_DSN_SERVER "Server" +#define ESODBC_DSN_PORT "Port" +#define ESODBC_DSN_SECURE "Secure" +#define ESODBC_DSN_TIMEOUT "Timeout" +#define ESODBC_DSN_FOLLOW "Follow" +#define ESODBC_DSN_CATALOG "Catalog" +#define ESODBC_DSN_PACKING "Packing" +#define ESODBC_DSN_MAX_FETCH_SIZE "MaxFetchSize" +#define ESODBC_DSN_MAX_BODY_SIZE_MB "MaxBodySizeMB" +#define ESODBC_DSN_TRACE_FILE "TraceFile" +#define ESODBC_DSN_TRACE_LEVEL "TraceLevel" + +/* stucture to collect all attributes in a connection string */ +typedef struct { + wstr_st driver; + wstr_st description; + wstr_st dsn; + wstr_st pwd; + wstr_st uid; + wstr_st savefile; + wstr_st filedsn; + wstr_st server; + wstr_st port; + wstr_st secure; + wstr_st timeout; + wstr_st follow; + wstr_st catalog; + wstr_st packing; + wstr_st max_fetch_size; + wstr_st max_body_size; + wstr_st trace_file; + wstr_st trace_level; +#define ESODBC_DSN_ATTRS_COUNT 18 + SQLWCHAR buff[ESODBC_DSN_ATTRS_COUNT * ESODBC_DSN_MAX_ATTR_LEN]; +} esodbc_dsn_attrs_st; + +void init_dsn_attrs(esodbc_dsn_attrs_st *attrs); +BOOL assign_dsn_defaults(esodbc_dsn_attrs_st *attrs); +BOOL assign_dsn_attr(esodbc_dsn_attrs_st *attrs, + wstr_st *keyword, wstr_st *value, BOOL overwrite); + +BOOL read_system_info(esodbc_dsn_attrs_st *attrs); +int system_dsn_exists(wstr_st *dsn); +BOOL load_system_dsn(esodbc_dsn_attrs_st *attrs, SQLWCHAR *list00); +BOOL write_system_dsn(esodbc_dsn_attrs_st *attrs, BOOL create_new); + +BOOL parse_connection_string(esodbc_dsn_attrs_st *attrs, + SQLWCHAR *szConnStrIn, SQLSMALLINT cchConnStrIn); +BOOL write_connection_string(esodbc_dsn_attrs_st *attrs, + SQLWCHAR *szConnStrOut, SQLSMALLINT cchConnStrOutMax, + SQLSMALLINT *pcchConnStrOut); + +BOOL prompt_user_config(HWND hwndParent, esodbc_dsn_attrs_st *attrs, + BOOL disable_nonconn); +int prompt_user_overwrite(HWND hwndParent, wstr_st *dsn); + +#endif /* __DSN_H__ */ + + +/* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : */ diff --git a/driver/handles.c b/driver/handles.c index 25abce18..7fae5696 100644 --- a/driver/handles.c +++ b/driver/handles.c @@ -2329,7 +2329,7 @@ SQLRETURN EsSQLSetDescFieldW( case SQL_DESC_NAME: WARNH(desc, "stored procedure params (to set to `"LWPD"`) not " - "supported.", ValuePtr ? (SQLWCHAR *)ValuePtr : TS_NULL); + "supported.", ValuePtr ? (SQLWCHAR *)ValuePtr : TWS_NULL); RET_HDIAG(desc, SQL_STATE_HYC00, "stored procedure params not supported", 0); diff --git a/driver/info.c b/driver/info.c index aefbcceb..a170235e 100644 --- a/driver/info.c +++ b/driver/info.c @@ -12,13 +12,7 @@ #include "handles.h" #include "queries.h" #include "connect.h" - -#if ODBCVER == 0x0380 -/* String constant for supported ODBC version */ -#define ESODBC_SQL_SPEC_STRING "03.80" -#else /* ver==3.8 */ -#error "unsupported ODBC version" -#endif /* ver==3.8 */ +#include "info.h" #define ORIG_DISCRIM "IM" #define ORIG_CLASS_ISO "ISO 9075" diff --git a/driver/info.h b/driver/info.h index 80da76df..e80f614c 100644 --- a/driver/info.h +++ b/driver/info.h @@ -9,6 +9,12 @@ #include "sqlext.h" +#if ODBCVER == 0x0380 +/* String constant for supported ODBC version */ +#define ESODBC_SQL_SPEC_STRING "03.80" +#else /* ver==3.8 */ +#error "unsupported ODBC version" +#endif /* ver==3.8 */ SQLRETURN EsSQLGetInfoW(SQLHDBC ConnectionHandle, SQLUSMALLINT InfoType, diff --git a/driver/log.h b/driver/log.h index 4dba7989..67bde196 100644 --- a/driver/log.h +++ b/driver/log.h @@ -94,7 +94,9 @@ extern int _esodbc_log_level; #define FIXME BUG("not yet implemented") #define TRACE DBG("===== TR4C3 ====="); -#define TS_NULL MK_WPTR("") +/* NULL as ("to Wide") string */ +#define TWS_NULL MK_WPTR("") +#define TS_NULL MK_CPTR("") /* diff --git a/driver/setup.c b/driver/setup.c new file mode 100644 index 00000000..a69e22f4 --- /dev/null +++ b/driver/setup.c @@ -0,0 +1,263 @@ +/* + * 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. + */ +#include "util.h" /* includes windows.h, needed for odbcinst.h */ +#include + +#include "defs.h" +#include "tracing.h" +#include "log.h" +#include "info.h" +#include "dsn.h" + +#define VAL_NAME_APILEVEL "APILevel" +#define VAL_NAME_CONNECTFN "ConnectFunctions" +#define VAL_NAME_ODBCVER "DriverODBCVer" +#define VAL_NAME_SQLLEVEL "SQLLevel" + +/* + * Adds vals from: + * https://docs.microsoft.com/en-us/sql/odbc/reference/install/driver-specification-subkeys + */ +static BOOL add_subkey_values(LPCWSTR lpszDriver) +{ + SQLWCHAR buff[sizeof("YYY")] = {0}; + esodbc_dbc_st dbc; + SQLRETURN ret = SQL_SUCCESS; + SQLSMALLINT supported; + + assert(ESODBC_ODBC_INTERFACE_CONFORMANCE <= 9); + memset(buff, 0, sizeof(buff)); + buff[0] = MK_WPTR('0') + ESODBC_ODBC_INTERFACE_CONFORMANCE; + if (! SQLWritePrivateProfileStringW(lpszDriver, + MK_WPTR(VAL_NAME_APILEVEL), + buff, MK_WPTR(SUBKEY_ODBCINST))) { + ERR("failed to add value `" VAL_NAME_APILEVEL "`."); + return FALSE; + } + + memset(buff, 0, sizeof(buff)); + memset(&dbc, 0, sizeof(dbc)); + dbc.hdr.type = SQL_HANDLE_DBC; + ret |= EsSQLGetFunctions(&dbc, SQL_API_SQLCONNECT, &supported); + buff[0] = supported ? MK_WPTR('Y') : MK_WPTR('N'); + ret |= EsSQLGetFunctions(&dbc, SQL_API_SQLDRIVERCONNECT, &supported); + buff[1] = supported ? MK_WPTR('Y') : MK_WPTR('N'); + ret |= EsSQLGetFunctions(&dbc, SQL_API_SQLBROWSECONNECT, &supported); + buff[2] = supported ? MK_WPTR('Y') : MK_WPTR('N'); + if (! SQL_SUCCEEDED(ret)) { + BUG("failed to read connect functions support."); + return FALSE; + } + if (! SQLWritePrivateProfileStringW(lpszDriver, + MK_WPTR(VAL_NAME_CONNECTFN), + buff, MK_WPTR(SUBKEY_ODBCINST))) { + ERR("failed to add value `" VAL_NAME_CONNECTFN "`."); + return FALSE; + } + + if (! SQLWritePrivateProfileStringW(lpszDriver, + MK_WPTR(VAL_NAME_ODBCVER), + MK_WPTR(ESODBC_SQL_SPEC_STRING), MK_WPTR(SUBKEY_ODBCINST))) { + ERR("failed to add value `" VAL_NAME_APILEVEL "`."); + return FALSE; + } + + assert(ESODBC_SQL_CONFORMANCE <= 9); + memset(buff, 0, sizeof(buff)); + buff[0] = MK_WPTR('0') + ESODBC_SQL_CONFORMANCE; + if (! SQLWritePrivateProfileStringW(lpszDriver, + MK_WPTR(VAL_NAME_SQLLEVEL), + buff, MK_WPTR(SUBKEY_ODBCINST))) { + ERR("failed to add value `" VAL_NAME_APILEVEL "`."); + return FALSE; + } + + return TRUE; +} + +/* called for every installation and for last removal */ +BOOL SQL_API ConfigDriverW( + HWND hwndParent, + WORD fRequest, + LPCWSTR lpszDriver, + LPCWSTR lpszArgs, + LPWSTR lpszMsg, + WORD cbMsgMax, + WORD *pcbMsgOut) +{ + BOOL ret = FALSE; +# define _DSN_END_MARKER "\\" + SQLWCHAR *sample_dsn = MK_WPTR("DSN=" + ESODBC_DSN_SAMPLE_NAME + _DSN_END_MARKER); + SQLWCHAR *pos; + + TRACE7(_IN, "phWWpht", hwndParent, fRequest, lpszDriver, lpszArgs, + lpszMsg, cbMsgMax, pcbMsgOut); + + switch (fRequest) { + case ODBC_INSTALL_DRIVER: + ret = add_subkey_values(lpszDriver); + if (! ret) { + SQLPostInstallerError(ODBC_ERROR_REQUEST_FAILED, NULL); + } + /* add the 2nd \0 to have a 00-list */ + pos = wcschr(sample_dsn, _DSN_END_MARKER[0]); + assert(pos); + *pos = _MK_WPTR('\0'); + /* add a sample DSN */ + if (! ConfigDSNW(NULL, ODBC_ADD_DSN, lpszDriver, sample_dsn)) { + WARN("failed to provision a sample DSN."); + /* no further error indication though, install succeedes */ + } + break; + case ODBC_REMOVE_DRIVER: + /* nothing to do, the vales in ODBCINST.INI are removed along with + * the subkey by caller, when needed and same happens with DSNs + * under ODBC.INI */ + ret = TRUE; + break; + default: + ERR("unexpected configuration request type: %hd.", fRequest); + SQLPostInstallerError(ODBC_ERROR_INVALID_REQUEST_TYPE, NULL); + goto end; + } +end: + TRACE8(_OUT, "dphWWpht", ret, hwndParent, fRequest, lpszDriver, lpszArgs, + lpszMsg, cbMsgMax, pcbMsgOut); + return ret; +# undef _DSN_END_MARKER +} + +BOOL SQL_API ConfigDSNW( + HWND hwndParent, + WORD fRequest, + LPCWSTR lpszDriver, + LPCWSTR lpszAttributes) +{ + esodbc_dsn_attrs_st attrs; + wstr_st driver; + SQLWCHAR buff[ESODBC_DSN_MAX_ATTR_LEN] = {0}; + wstr_st old_dsn = {buff, 0}; + BOOL create_new; + int res; + BOOL ret = FALSE; + + TRACE4(_IN, "phWW", hwndParent, fRequest, lpszDriver, lpszAttributes); + + init_dsn_attrs(&attrs); + + /* If there's a DSN in reveived attributes, load the config from the + * registry. Otherwise, populate a new config with defaults. */ + if (! load_system_dsn(&attrs, (SQLWCHAR *)lpszAttributes)) { + ERR("failed to load system DSN for driver ` " LWPD " ` and " + "attributes `" LWPD "`.", lpszDriver, lpszAttributes); + return FALSE;; + } + /* assign the Driver name; this is not the value of the Driver key in the + * registry (i.e. the path to the DLL), which is actually skipped when + * loading the config. */ + driver = (wstr_st) { + (SQLWCHAR *)lpszDriver, + wcslen(lpszDriver) + }; + res = assign_dsn_attr(&attrs, &MK_WSTR(ESODBC_DSN_DRIVER), &driver, + /*overwrite?*/FALSE); + assert(0 < res); + + switch (fRequest) { + case ODBC_CONFIG_DSN: + /* save the DSN naming, since this might be changed by the user */ + if (attrs.dsn.cnt) { + /* attrs.dsn.cnt < ESODBC_DSN_MAX_ATTR_LEN due to + * load_sys_dsn() */ + wcscpy(old_dsn.str, attrs.dsn.str); + old_dsn.cnt = attrs.dsn.cnt; + } + case ODBC_ADD_DSN: + /* user-interraction loop */ + while (TRUE) { + res = prompt_user_config(hwndParent, &attrs, FALSE); + if (res < 0) { + ERR("failed getting user values."); + goto err; + } else if (! res) { + INFO("user canceled the dialog."); + goto end; + } + /* is it a brand new DSN or has the DSN name changed? */ + DBG("old DSN: `" LWPDL "`, new DSN: `" LWPDL "`.", + LWSTR(&old_dsn), LWSTR(&attrs.dsn)); + if ((! old_dsn.cnt) || + (! EQ_CASE_WSTR(&old_dsn, &attrs.dsn))) { + /* check if target DSN (new or old) already exists */ + res = system_dsn_exists(&attrs.dsn); + if (res < 0) { + ERR("failed to check if DSN `" LWPDL "` already " + "exists.", LWSTR(&attrs.dsn)); + goto err; + } else if (res) { + res = prompt_user_overwrite(hwndParent, &attrs.dsn); + if (res < 0) { + ERR("failed to get user input."); + goto err; + } else if (! res) { + /* let user chose a different DSN name */ + continue; + } + } + /* if an old DSN exists, delete it */ + if (old_dsn.cnt) { + if (! SQLRemoveDSNFromIniW(old_dsn.str)) { + ERR("failed to remove old DSN ` " LWPDL " `.", + LWSTR(&old_dsn)); + goto err; + } + DBG("removed now renamed DSN `" LWPDL "`.", + LWSTR(&old_dsn)); + } + create_new = TRUE; + } else { + create_new = FALSE; + } + break; + } + /* create or update the DSN */ + if (! write_system_dsn(&attrs, create_new)) { + ERR("failed to add DSN to the system."); + } else { + ret = TRUE; + } + break; + + case ODBC_REMOVE_DSN: + if (! SQLRemoveDSNFromIniW(attrs.dsn.str)) { + ERR("failed to remove driver ` " LWPD " ` with " + "attributes `" LWPD "`.", lpszDriver, lpszAttributes); + } else { + INFO("removed DSN `" LWPDL "` from the system.", + LWSTR(&attrs.dsn)); + ret = TRUE; + } + break; + + default: + ERR("unexpected configuration request type: %hd.", fRequest); + SQLPostInstallerError(ODBC_ERROR_INVALID_REQUEST_TYPE, NULL); + goto end; + } + +err: + if (! ret) { + SQLPostInstallerError(ODBC_ERROR_REQUEST_FAILED, NULL); + } +end: + TRACE5(_OUT, "dphWW", ret, hwndParent, fRequest, lpszDriver, + lpszAttributes); + return ret; +} + +/* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : */ diff --git a/driver/tracing.h b/driver/tracing.h index 57900525..0e3e2dac 100644 --- a/driver/tracing.h +++ b/driver/tracing.h @@ -14,11 +14,14 @@ #define _AVAIL sizeof(_bf) - _ps +/*INDENT-OFF*/ /* TODO: the SQL[U]LEN for _WIN32 */ #define _PRINT_PARAM_VAL(type, val) \ do { \ switch(type) { \ - /* numeric pointers */ \ + /* + * numeric pointers + */ \ /* SQLNUMERIC/SQLDATE/SQLCHAR/etc. = unsigned char */ \ /* SQLSCHAR = char */ \ case 'c': /* char signed */ \ @@ -56,18 +59,29 @@ _n = snprintf(_bf + _ps, _AVAIL, "%llu", \ val ? *(uint64_t *)(uintptr_t)val : 0); \ break; \ - /* non-numeric pointers */ \ + /* + * non-numeric pointers + */ \ case 'p': /* void* */ \ _n = snprintf(_bf + _ps, _AVAIL, "@0x%p", \ (void *)(uintptr_t)val); \ break; \ case 'W': /* wchar_t* */ \ - /* TODO: problematic for untouched buffs: add len! */ \ + /* TODO: scan for unsafe usages with uninit'ed buffers */ \ + /* Note: problematic for untouched buffs: 'p' for unsure. */ \ _n = snprintf(_bf + _ps, _AVAIL, "`" LWPD "`[%zd]", \ - val ? (wchar_t *)(uintptr_t)val : TS_NULL, \ + val ? (wchar_t *)(uintptr_t)val : TWS_NULL, \ val ? wcslen((wchar_t *)(uintptr_t)val) : 0); \ break; \ - /* imediat values */ \ + case 's': /* char* */ \ + /* Note: problematic for untouched buffs: 'p' for unsure. */ \ + _n = snprintf(_bf + _ps, _AVAIL, "`" LCPD "`[%zd]", \ + val ? (char *)(uintptr_t)val : TS_NULL, \ + val ? strlen((char *)(uintptr_t)val) : 0); \ + break; \ + /* + * imediat values + */ \ /* longs */ \ case 'l': /* long signed */ \ _n = snprintf(_bf + _ps, _AVAIL, "%ld", \ @@ -86,6 +100,14 @@ _n = snprintf(_bf + _ps, _AVAIL, "%u", \ (unsigned)(uintptr_t)val); \ break;\ + case 'h': /* short signed */ \ + _n = snprintf(_bf + _ps, _AVAIL, "%hd", \ + (short)(intptr_t)val); \ + break;\ + case 'H': /* short unsigned */ \ + _n = snprintf(_bf + _ps, _AVAIL, "%hu", \ + (unsigned short)(uintptr_t)val); \ + break;\ default: \ _n = snprintf(_bf+_ps, _AVAIL, "BUG! unknown type: %d",type); \ break; \ @@ -93,6 +115,7 @@ if (0 < _n) \ _ps += _n; \ } while (0) +/*INDENT-ON*/ #define _IS_PTR(type, _is_ptr) \ do {\ @@ -101,6 +124,8 @@ case 'L': \ case 'd': \ case 'u': \ + case 'h': \ + case 'H': \ _is_ptr = FALSE; \ break; \ default: \ diff --git a/driver/util.c b/driver/util.c index 947846ca..a583eae6 100644 --- a/driver/util.c +++ b/driver/util.c @@ -85,7 +85,7 @@ BOOL str2ubigint(void *val, const BOOL wide, SQLUBIGINT *out) BOOL str2bigint(void *val, const BOOL wide, SQLBIGINT *out) { SQLUBIGINT ull; /* unsigned long long */ - size_t i, at0; + size_t i; BOOL negative, ret; cstr_st cstr; wstr_st wstr; @@ -93,18 +93,16 @@ BOOL str2bigint(void *val, const BOOL wide, SQLBIGINT *out) if (wide) { wstr = *(wstr_st *)val; i = wstr.cnt; - at0 = wstr.str[0]; } else { cstr = *(cstr_st *)val; i = cstr.cnt; - at0 = cstr.str[0]; } if (i < 1) { errno = EINVAL; return FALSE; } else { - switch (at0) { + switch (wide ? wstr.str[0] : cstr.str[0]) { case '-': /* L'-' =(size_t)= '-' */ negative = TRUE; i = 1;