driver/dsn.c (892 lines of code) (raw):
/*
* 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 <odbcinst.h>
#include "dsn.h"
#include "log.h"
#include "connect.h"
#include "info.h"
#include "util.h"
void TEST_API 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];
}
}
/* used with LWSTR() macro, so invoked twice, but it is:
* - executed only if the message level is low enough (i.e. it's logged);
* - more compact this way. */
static inline wstr_st *mask_pwd(wstr_st *attr, wstr_st *val)
{
static wstr_st subst = WSTR_INIT(ESODBC_PWD_VAL_SUBST);
return EQ_CASE_WSTR(attr, &MK_WSTR(ESODBC_DSN_PWD)) ||
EQ_CASE_WSTR(attr, &MK_WSTR(ESODBC_DSN_API_KEY)) ? &subst : val;
}
#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_API_KEY), &attrs->api_key},
{&MK_WSTR(ESODBC_DSN_SAVEFILE), &attrs->savefile},
{&MK_WSTR(ESODBC_DSN_FILEDSN), &attrs->filedsn},
{&MK_WSTR(ESODBC_DSN_CLOUD_ID), &attrs->cloud_id},
{&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},
{&MK_WSTR(ESODBC_DSN_PACKING), &attrs->packing},
{&MK_WSTR(ESODBC_DSN_COMPRESSION), &attrs->compression},
{&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_APPLY_TZ), &attrs->apply_tz},
{&MK_WSTR(ESODBC_DSN_EARLY_EXEC), &attrs->early_exec},
{&MK_WSTR(ESODBC_DSN_SCI_FLOATS), &attrs->sci_floats},
{&MK_WSTR(ESODBC_DSN_VARCHAR_LIMIT), &attrs->varchar_limit},
{&MK_WSTR(ESODBC_DSN_MFIELD_LENIENT), &attrs->mfield_lenient},
{&MK_WSTR(ESODBC_DSN_ESC_PVA), &attrs->auto_esc_pva},
{&MK_WSTR(ESODBC_DSN_IDX_INC_FROZEN), &attrs->idx_inc_frozen},
{&MK_WSTR(ESODBC_DSN_PROXY_ENABLED), &attrs->proxy_enabled},
{&MK_WSTR(ESODBC_DSN_PROXY_TYPE), &attrs->proxy_type},
{&MK_WSTR(ESODBC_DSN_PROXY_HOST), &attrs->proxy_host},
{&MK_WSTR(ESODBC_DSN_PROXY_PORT), &attrs->proxy_port},
{&MK_WSTR(ESODBC_DSN_PROXY_AUTH_ENA), &attrs->proxy_auth_enabled},
{&MK_WSTR(ESODBC_DSN_PROXY_AUTH_UID), &attrs->proxy_auth_uid},
{&MK_WSTR(ESODBC_DSN_PROXY_AUTH_PWD), &attrs->proxy_auth_pwd},
{&MK_WSTR(ESODBC_DSN_TRACE_ENABLED), &attrs->trace_enabled},
{&MK_WSTR(ESODBC_DSN_TRACE_FILE), &attrs->trace_file},
{&MK_WSTR(ESODBC_DSN_TRACE_LEVEL), &attrs->trace_level},
{NULL, NULL}
};
int ret;
assert(sizeof(map)/sizeof(*iter) - /* {NULL,NULL} terminator */1 ==
ESODBC_DSN_ATTRS_COUNT);
if (ESODBC_DSN_MAX_ATTR_LEN < value->cnt) {
ERR("attribute value length 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(mask_pwd(iter->kw, value)),
LWSTR(mask_pwd(iter->kw, iter->val)));
return DSN_NOT_OVERWRITTEN;
}
INFO("keyword '" LWPDL "' already assigned: "
"overwriting previous `" LWPDL "` with new `" LWPDL "`.",
LWSTR(iter->kw), LWSTR(mask_pwd(iter->kw, iter->val)),
LWSTR(mask_pwd(iter->kw, value)));
ret = DSN_OVERWRITTEN;
} else {
INFO("keyword '" LWPDL "' new assignment: `" LWPDL "`.",
LWSTR(iter->kw), LWSTR(mask_pwd(iter->kw, 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 TEST_API 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;
/* if NTS, parse till skip_ws() encounters the \0 */
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)) {
continue; /* empty values are acceptable */
}
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(mask_pwd(&keyword, &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 TEST_API 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 length (%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 inline BOOL needs_braces(wstr_st *token)
{
size_t 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;
}
long TEST_API write_00_list(esodbc_dsn_attrs_st *attrs,
SQLWCHAR *list00, size_t cnt00)
{
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_API_KEY), &attrs->api_key},
{&MK_WSTR(ESODBC_DSN_SAVEFILE), &attrs->savefile},
{&MK_WSTR(ESODBC_DSN_FILEDSN), &attrs->filedsn},
{&MK_WSTR(ESODBC_DSN_CLOUD_ID), &attrs->cloud_id},
{&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},
{&MK_WSTR(ESODBC_DSN_PACKING), &attrs->packing},
{&MK_WSTR(ESODBC_DSN_COMPRESSION), &attrs->compression},
{&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_APPLY_TZ), &attrs->apply_tz},
{&MK_WSTR(ESODBC_DSN_EARLY_EXEC), &attrs->early_exec},
{&MK_WSTR(ESODBC_DSN_SCI_FLOATS), &attrs->sci_floats},
{&MK_WSTR(ESODBC_DSN_VARCHAR_LIMIT), &attrs->varchar_limit},
{&MK_WSTR(ESODBC_DSN_MFIELD_LENIENT), &attrs->mfield_lenient},
{&MK_WSTR(ESODBC_DSN_ESC_PVA), &attrs->auto_esc_pva},
{&MK_WSTR(ESODBC_DSN_IDX_INC_FROZEN), &attrs->idx_inc_frozen},
{&MK_WSTR(ESODBC_DSN_PROXY_ENABLED), &attrs->proxy_enabled},
{&MK_WSTR(ESODBC_DSN_PROXY_TYPE), &attrs->proxy_type},
{&MK_WSTR(ESODBC_DSN_PROXY_HOST), &attrs->proxy_host},
{&MK_WSTR(ESODBC_DSN_PROXY_PORT), &attrs->proxy_port},
{&MK_WSTR(ESODBC_DSN_PROXY_AUTH_ENA), &attrs->proxy_auth_enabled},
{&MK_WSTR(ESODBC_DSN_PROXY_AUTH_UID), &attrs->proxy_auth_uid},
{&MK_WSTR(ESODBC_DSN_PROXY_AUTH_PWD), &attrs->proxy_auth_pwd},
{&MK_WSTR(ESODBC_DSN_TRACE_ENABLED), &attrs->trace_enabled},
{&MK_WSTR(ESODBC_DSN_TRACE_FILE), &attrs->trace_file},
{&MK_WSTR(ESODBC_DSN_TRACE_LEVEL), &attrs->trace_level},
{NULL, NULL}
};
size_t pos;
BOOL add_braces;
/* check that the esodbc_dsn_attrs_st stays in sync with the above */
assert(sizeof(map)/sizeof(*iter) - /* {NULL,NULL} terminator */1 ==
ESODBC_DSN_ATTRS_COUNT);
for (iter = &map[0], pos = 0; iter->val; iter ++) {
if (! iter->val->cnt) {
continue;
}
/* the braces aren't really needed in a doubly-null-terminated list,
* but would make a conversion to a "normal" (`;` or `|` separated)
* connection string easy */
add_braces = needs_braces(iter->val);
if (cnt00 - /*final \0*/1 < pos + iter->kw->cnt + /*`=`*/1 +
(add_braces ? 2 : 0) + iter->val->cnt) {
ERR("not enough room in destination buffer.");
return -1;
}
/* copy keyword */
memcpy(list00 + pos, iter->kw->str, iter->kw->cnt * sizeof(*list00));
pos += iter->kw->cnt;
/* copy attribute value separator (`=`) and brace if needed */
list00[pos ++] = L'=';
if (add_braces) {
list00[pos ++] = L'{';
}
/* copy value */
memcpy(list00 + pos, iter->val->str, iter->val->cnt * sizeof(*list00));
pos += iter->val->cnt;
/* close any open brace */
if (add_braces) {
list00[pos ++] = L'}';
}
/* close current attribute */
list00[pos ++] = L'\0';
}
assert(pos < cnt00);
list00[pos ++] = L'\0';
return (long)pos;
}
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/sizeof(*buff), buff);
}
}
size_t copy_installer_errors(wchar_t *err_buff, size_t eb_max)
{
DWORD ecode;
SQLWCHAR buff[SQL_MAX_MESSAGE_LENGTH];
WORD msg_len;
int i, res;
size_t pos;
i = 0;
pos = 0;
while (SQL_SUCCEEDED(SQLInstallerError(++ i, &ecode, buff,
sizeof(buff)/sizeof(buff[0]), &msg_len))) {
/* Note: msg_len is actually count of chars, current doc is wrong */
/* if message is larger than buffer, the untruncated size is returned*/
if (sizeof(buff)/sizeof(buff[0]) <= msg_len) {
msg_len = sizeof(buff)/sizeof(buff[0]) ;
}
assert(pos <= eb_max);
DBG("error #%d: " LWPD " [%d].", i, buff, msg_len);
res = swprintf(err_buff + pos, eb_max - pos, L"%.*s. (code: %d)\n",
msg_len, buff, ecode);
if (res < 0) {
ERR("failed to copy: #%i: `" LWPDL "` (%d).", i, msg_len, buff,
ecode);
/* allow execution to continue, though */
} else {
pos += (size_t)res;
if (pos <= msg_len) {
WARN("reached error buffer end (%zu) before copying all "
"errors.", eb_max);
break;
}
}
}
return pos;
}
/*
* 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 DSN in the system (User/System section config mode dependent,
* SQLSetConfigMode()/~Get~).
* Returns positive on success, 0 on local failure, -1 on system failure.
*/
int load_system_dsn(esodbc_dsn_attrs_st *attrs, BOOL overwrite)
{
int res;
SQLWCHAR buff[ESODBC_DSN_MAX_ATTR_LEN], *pos;
SQLWCHAR kbuff[sizeof(attrs->buff)/sizeof(attrs->buff[0])];
wstr_st keyword, value;
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.");
return -1;
}
/* 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));
return -1;
} else {
value.cnt = (size_t)res;
value.str = buff;
}
/* assign it to the config */
DBG("read DSN attribute: `" LWPDL "` = `" LWPDL "`.",
LWSTR(&keyword), LWSTR(mask_pwd(&keyword, &value)));
/* assign attributes not yet given in the list */
if (assign_dsn_attr(attrs, &keyword, &value, overwrite) < 0) {
ERR("keyword '" LWPDL "' couldn't be assigned.", LWSTR(&keyword));
return 0;
}
}
return 1;
}
BOOL write_system_dsn(esodbc_dsn_attrs_st *new_attrs,
esodbc_dsn_attrs_st *old_attrs)
{
struct {
wstr_st *kw;
wstr_st *new;
wstr_st *old;
} *iter, map[] = {
/* Driver */
{
&MK_WSTR(ESODBC_DSN_DESCRIPTION), &new_attrs->description,
old_attrs ? &old_attrs->description : NULL
},
/* DSN */
{
&MK_WSTR(ESODBC_DSN_PWD), &new_attrs->pwd,
old_attrs ? &old_attrs->pwd : NULL
},
{
&MK_WSTR(ESODBC_DSN_UID), &new_attrs->uid,
old_attrs ? &old_attrs->uid : NULL
},
{
&MK_WSTR(ESODBC_DSN_API_KEY), &new_attrs->api_key,
old_attrs ? &old_attrs->api_key : NULL
},
/* SAVEILE */
/* FILEDSN */
{
&MK_WSTR(ESODBC_DSN_CLOUD_ID), &new_attrs->cloud_id,
old_attrs ? &old_attrs->cloud_id : NULL
},
{
&MK_WSTR(ESODBC_DSN_SERVER), &new_attrs->server,
old_attrs ? &old_attrs->server : NULL
},
{
&MK_WSTR(ESODBC_DSN_PORT), &new_attrs->port,
old_attrs ? &old_attrs->port : NULL
},
{
&MK_WSTR(ESODBC_DSN_SECURE), &new_attrs->secure,
old_attrs ? &old_attrs->secure : NULL
},
{
&MK_WSTR(ESODBC_DSN_CA_PATH), &new_attrs->ca_path,
old_attrs ? &old_attrs->ca_path : NULL
},
{
&MK_WSTR(ESODBC_DSN_TIMEOUT), &new_attrs->timeout,
old_attrs ? &old_attrs->timeout : NULL
},
{
&MK_WSTR(ESODBC_DSN_FOLLOW), &new_attrs->follow,
old_attrs ? &old_attrs->follow : NULL
},
{
&MK_WSTR(ESODBC_DSN_CATALOG), &new_attrs->catalog,
old_attrs ? &old_attrs->catalog : NULL
},
{
&MK_WSTR(ESODBC_DSN_PACKING), &new_attrs->packing,
old_attrs ? &old_attrs->packing : NULL
},
{
&MK_WSTR(ESODBC_DSN_COMPRESSION), &new_attrs->compression,
old_attrs ? &old_attrs->packing : NULL
},
{
&MK_WSTR(ESODBC_DSN_MAX_FETCH_SIZE), &new_attrs->max_fetch_size,
old_attrs ? &old_attrs->max_fetch_size : NULL
},
{
&MK_WSTR(ESODBC_DSN_MAX_BODY_SIZE_MB), &new_attrs->max_body_size,
old_attrs ? &old_attrs->max_body_size : NULL
},
{
&MK_WSTR(ESODBC_DSN_APPLY_TZ), &new_attrs->apply_tz,
old_attrs ? &old_attrs->apply_tz : NULL
},
{
&MK_WSTR(ESODBC_DSN_EARLY_EXEC), &new_attrs->early_exec,
old_attrs ? &old_attrs->early_exec : NULL
},
{
&MK_WSTR(ESODBC_DSN_SCI_FLOATS), &new_attrs->sci_floats,
old_attrs ? &old_attrs->sci_floats : NULL
},
{
&MK_WSTR(ESODBC_DSN_VARCHAR_LIMIT), &new_attrs->varchar_limit,
old_attrs ? &old_attrs->varchar_limit : NULL
},
{
&MK_WSTR(ESODBC_DSN_MFIELD_LENIENT),
&new_attrs->mfield_lenient,
old_attrs ? &old_attrs->mfield_lenient : NULL
},
{
&MK_WSTR(ESODBC_DSN_ESC_PVA),
&new_attrs->auto_esc_pva,
old_attrs ? &old_attrs->auto_esc_pva : NULL
},
{
&MK_WSTR(ESODBC_DSN_IDX_INC_FROZEN),
&new_attrs->idx_inc_frozen,
old_attrs ? &old_attrs->idx_inc_frozen : NULL
},
{
&MK_WSTR(ESODBC_DSN_PROXY_ENABLED), &new_attrs->proxy_enabled,
old_attrs ? &old_attrs->proxy_enabled : NULL
},
{
&MK_WSTR(ESODBC_DSN_PROXY_TYPE), &new_attrs->proxy_type,
old_attrs ? &old_attrs->proxy_type : NULL
},
{
&MK_WSTR(ESODBC_DSN_PROXY_HOST), &new_attrs->proxy_host,
old_attrs ? &old_attrs->proxy_host : NULL
},
{
&MK_WSTR(ESODBC_DSN_PROXY_PORT), &new_attrs->proxy_port,
old_attrs ? &old_attrs->proxy_port : NULL
},
{
&MK_WSTR(ESODBC_DSN_PROXY_AUTH_ENA),
&new_attrs->proxy_auth_enabled,
old_attrs ? &old_attrs->proxy_auth_enabled : NULL
},
{
&MK_WSTR(ESODBC_DSN_PROXY_AUTH_UID), &new_attrs->proxy_auth_uid,
old_attrs ? &old_attrs->proxy_auth_uid : NULL
},
{
&MK_WSTR(ESODBC_DSN_PROXY_AUTH_PWD), &new_attrs->proxy_auth_pwd,
old_attrs ? &old_attrs->proxy_auth_pwd : NULL
},
{
&MK_WSTR(ESODBC_DSN_TRACE_ENABLED), &new_attrs->trace_enabled,
old_attrs ? &old_attrs->trace_enabled : NULL
},
{
&MK_WSTR(ESODBC_DSN_TRACE_FILE), &new_attrs->trace_file,
old_attrs ? &old_attrs->trace_file : NULL
},
{
&MK_WSTR(ESODBC_DSN_TRACE_LEVEL), &new_attrs->trace_level,
old_attrs ? &old_attrs->trace_level : NULL
},
{NULL, NULL, NULL}
};
/* check that the esodbc_dsn_attrs_st stays in sync with the above */
assert(sizeof(map)/sizeof(map[0]) /* {NULL,NULL, NULL} terminator */- 1
/*Driver,DSN,SAVEFILE,FILEDSN*/+ 4 == ESODBC_DSN_ATTRS_COUNT);
for (iter = &map[0]; iter->kw; iter ++) {
if (iter->old) {
if (EQ_WSTR(iter->new, iter->old)) {
DBG("DSN `" LWPDL "` attribute " LWPDL " maintained "
"value `" LWPDL "`.", LWSTR(&new_attrs->dsn),
LWSTR(iter->kw), LWSTR(mask_pwd(iter->kw, iter->new)));
continue;
}
if (! SQLWritePrivateProfileStringW(new_attrs->dsn.str,
iter->kw->str,
/* "If this argument is NULL, the key pointed to by the
* lpszEntry argument is deleted." */
iter->new->cnt ? iter->new->str : NULL,
MK_WPTR(SUBKEY_ODBC))) {
ERR("failed to write key `" LWPDL "` with value `" LWPDL "`.",
LWSTR(iter->kw), LWSTR(mask_pwd(iter->kw, iter->new)));
return FALSE;
}
INFO("DSN `" LWPDL "` attribute " LWPDL " set to `" LWPDL "`%s.",
LWSTR(&new_attrs->dsn), LWSTR(iter->kw),
LWSTR(mask_pwd(iter->kw, iter->new)),
iter->new->cnt ? "" : " (deleted)");
} else if (iter->new->cnt) {
if (! SQLWritePrivateProfileStringW(new_attrs->dsn.str,
iter->kw->str, iter->new->str, MK_WPTR(SUBKEY_ODBC))) {
ERR("failed to write key `" LWPDL "` with value `" LWPDL "`.",
LWSTR(iter->kw), LWSTR(mask_pwd(iter->kw, iter->new)));
return FALSE;
}
INFO("DSN `" LWPDL "` attribute " LWPDL " set to `" LWPDL "`.",
LWSTR(&new_attrs->dsn), LWSTR(iter->kw),
LWSTR(mask_pwd(iter->kw, iter->new)));
}
}
return TRUE;
}
/* build a connection string to be written in the DSN files */
long TEST_API write_connection_string(esodbc_dsn_attrs_st *attrs,
SQLWCHAR *szConnStrOut, SQLSMALLINT cchConnStrOutMax)
{
int n, braces;
size_t pos;
wchar_t *format;
struct {
wstr_st *val;
wstr_st *kw;
} *iter, map[] = {
{&attrs->driver, &MK_WSTR(ESODBC_DSN_DRIVER)},
{&attrs->description, &MK_WSTR(ESODBC_DSN_DESCRIPTION)},
{&attrs->dsn, &MK_WSTR(ESODBC_DSN_DSN)},
{&attrs->pwd, &MK_WSTR(ESODBC_DSN_PWD)},
{&attrs->uid, &MK_WSTR(ESODBC_DSN_UID)},
{&attrs->api_key, &MK_WSTR(ESODBC_DSN_API_KEY)},
{&attrs->savefile, &MK_WSTR(ESODBC_DSN_SAVEFILE)},
{&attrs->filedsn, &MK_WSTR(ESODBC_DSN_FILEDSN)},
{&attrs->cloud_id, &MK_WSTR(ESODBC_DSN_CLOUD_ID)},
{&attrs->server, &MK_WSTR(ESODBC_DSN_SERVER)},
{&attrs->port, &MK_WSTR(ESODBC_DSN_PORT)},
{&attrs->secure, &MK_WSTR(ESODBC_DSN_SECURE)},
{&attrs->ca_path, &MK_WSTR(ESODBC_DSN_CA_PATH)},
{&attrs->timeout, &MK_WSTR(ESODBC_DSN_TIMEOUT)},
{&attrs->follow, &MK_WSTR(ESODBC_DSN_FOLLOW)},
{&attrs->catalog, &MK_WSTR(ESODBC_DSN_CATALOG)},
{&attrs->packing, &MK_WSTR(ESODBC_DSN_PACKING)},
{&attrs->compression, &MK_WSTR(ESODBC_DSN_COMPRESSION)},
{&attrs->max_fetch_size, &MK_WSTR(ESODBC_DSN_MAX_FETCH_SIZE)},
{&attrs->max_body_size, &MK_WSTR(ESODBC_DSN_MAX_BODY_SIZE_MB)},
{&attrs->apply_tz, &MK_WSTR(ESODBC_DSN_APPLY_TZ)},
{&attrs->early_exec, &MK_WSTR(ESODBC_DSN_EARLY_EXEC)},
{&attrs->sci_floats, &MK_WSTR(ESODBC_DSN_SCI_FLOATS)},
{&attrs->varchar_limit, &MK_WSTR(ESODBC_DSN_VARCHAR_LIMIT)},
{&attrs->mfield_lenient, &MK_WSTR(ESODBC_DSN_MFIELD_LENIENT)},
{&attrs->auto_esc_pva, &MK_WSTR(ESODBC_DSN_ESC_PVA)},
{&attrs->idx_inc_frozen, &MK_WSTR(ESODBC_DSN_IDX_INC_FROZEN)},
{&attrs->proxy_enabled, &MK_WSTR(ESODBC_DSN_PROXY_ENABLED)},
{&attrs->proxy_type, &MK_WSTR(ESODBC_DSN_PROXY_TYPE)},
{&attrs->proxy_host, &MK_WSTR(ESODBC_DSN_PROXY_HOST)},
{&attrs->proxy_port, &MK_WSTR(ESODBC_DSN_PROXY_PORT)},
{&attrs->proxy_auth_enabled, &MK_WSTR(ESODBC_DSN_PROXY_AUTH_ENA)},
{&attrs->proxy_auth_uid, &MK_WSTR(ESODBC_DSN_PROXY_AUTH_UID)},
{&attrs->proxy_auth_pwd, &MK_WSTR(ESODBC_DSN_PROXY_AUTH_PWD)},
{&attrs->trace_enabled, &MK_WSTR(ESODBC_DSN_TRACE_ENABLED)},
{&attrs->trace_file, &MK_WSTR(ESODBC_DSN_TRACE_FILE)},
{&attrs->trace_level, &MK_WSTR(ESODBC_DSN_TRACE_LEVEL)},
{NULL, NULL}
};
/* check that the esodbc_dsn_attrs_st stays in sync with the above */
assert(sizeof(map)/sizeof(*iter) - /* {NULL,NULL} terminator */1 ==
ESODBC_DSN_ATTRS_COUNT);
assert(0 <= cchConnStrOutMax);
for (iter = &map[0], pos = 0; iter->val; iter ++) {
if (iter->val->cnt) {
braces = needs_braces(iter->val) ? 2 : 0;
if (cchConnStrOutMax && szConnStrOut) {
/* is there still room in the buffer? */
if ((SQLSMALLINT)pos < cchConnStrOutMax) {
if (braces) {
format = WPFWP_LDESC "={" WPFWP_LDESC "};";
} else {
format = WPFWP_LDESC "=" WPFWP_LDESC ";";
}
errno = 0;
n = swprintf(szConnStrOut + pos, cchConnStrOutMax - pos,
format, LWSTR(iter->kw), LWSTR(iter->val));
/* on buffer too small, swprintf() will 0-terminate it,
* return negative, but not set errno. */
if (n < 0) {
if (errno != 0) {
ERRN("failed to print connection string (keyword: "
LWPDL ", room: %hd, position: %zu).",
LWSTR(iter->kw), cchConnStrOutMax, pos);
return -1;
}
assert(szConnStrOut[cchConnStrOutMax - 1] == L'\0');
}
}
}
/* update the write position with what would be written (not what
* has been), since the untruncated length needs to always be
* returned to the app */
pos += iter->kw->cnt + /*`=`*/1 +
iter->val->cnt + braces + /*`;`*/1;
}
}
#ifndef NDEBUG /* don't print the PWD */
DBG("new connection string: `" LWPD "`; out len: %zu.", szConnStrOut, pos);
#endif /* NDEBUG */
assert(pos < LONG_MAX);
return (long)pos;
}
/* assign defaults where not assigned and applicable */
void assign_dsn_defaults(esodbc_dsn_attrs_st *attrs)
{
int res = 0;
if (! attrs->cloud_id.cnt) { /* the Cloud ID will provide these attrs */
res |= assign_dsn_attr(attrs,
&MK_WSTR(ESODBC_DSN_SERVER), &MK_WSTR(ESODBC_DEF_SERVER),
/*overwrite?*/FALSE);
res |= assign_dsn_attr(attrs,
&MK_WSTR(ESODBC_DSN_PORT), &MK_WSTR(ESODBC_DEF_PORT),
/*overwrite?*/FALSE);
res |= assign_dsn_attr(attrs,
&MK_WSTR(ESODBC_DSN_SECURE), &MK_WSTR(ESODBC_DEF_SECURE),
/*overwrite?*/FALSE);
}
res |= assign_dsn_attr(attrs,
&MK_WSTR(ESODBC_DSN_TIMEOUT), &MK_WSTR(ESODBC_DEF_TIMEOUT),
/*overwrite?*/FALSE);
res |= assign_dsn_attr(attrs,
&MK_WSTR(ESODBC_DSN_FOLLOW), &MK_WSTR(ESODBC_DEF_FOLLOW),
/*overwrite?*/FALSE);
res |= assign_dsn_attr(attrs,
&MK_WSTR(ESODBC_DSN_PACKING), &MK_WSTR(ESODBC_DEF_PACKING),
/*overwrite?*/FALSE);
res |= assign_dsn_attr(attrs,
&MK_WSTR(ESODBC_DSN_COMPRESSION), &MK_WSTR(ESODBC_DEF_COMPRESSION),
/*overwrite?*/FALSE);
res |= assign_dsn_attr(attrs,
&MK_WSTR(ESODBC_DSN_MAX_FETCH_SIZE),
&MK_WSTR(ESODBC_DEF_FETCH_SIZE),
/*overwrite?*/FALSE);
res |= assign_dsn_attr(attrs, &MK_WSTR(ESODBC_DSN_MAX_BODY_SIZE_MB),
&MK_WSTR(ESODBC_DEF_MAX_BODY_SIZE_MB),
/*overwrite?*/FALSE);
res |= assign_dsn_attr(attrs,
&MK_WSTR(ESODBC_DSN_APPLY_TZ), &MK_WSTR(ESODBC_DEF_APPLY_TZ),
/*overwrite?*/FALSE);
res |= assign_dsn_attr(attrs,
&MK_WSTR(ESODBC_DSN_EARLY_EXEC), &MK_WSTR(ESODBC_DEF_EARLY_EXEC),
/*overwrite?*/FALSE);
res |= assign_dsn_attr(attrs,
&MK_WSTR(ESODBC_DSN_SCI_FLOATS), &MK_WSTR(ESODBC_DEF_SCI_FLOATS),
/*overwrite?*/FALSE);
res |= assign_dsn_attr(attrs,
&MK_WSTR(ESODBC_DSN_VARCHAR_LIMIT),
&MK_WSTR(ESODBC_DEF_VARCHAR_LIMIT), /*overwrite?*/FALSE);
res |= assign_dsn_attr(attrs,
&MK_WSTR(ESODBC_DSN_MFIELD_LENIENT),
&MK_WSTR(ESODBC_DEF_MFIELD_LENIENT), /*overwrite?*/FALSE);
res |= assign_dsn_attr(attrs,
&MK_WSTR(ESODBC_DSN_ESC_PVA),
&MK_WSTR(ESODBC_DEF_ESC_PVA), /*overwrite?*/FALSE);
res |= assign_dsn_attr(attrs,
&MK_WSTR(ESODBC_DSN_IDX_INC_FROZEN),
&MK_WSTR(ESODBC_DEF_IDX_INC_FROZEN), /*overwrite?*/FALSE);
res |= assign_dsn_attr(attrs,
&MK_WSTR(ESODBC_DSN_PROXY_ENABLED),
&MK_WSTR(ESODBC_DEF_PROXY_ENABLED), /*overwrite?*/FALSE);
res |= assign_dsn_attr(attrs,
&MK_WSTR(ESODBC_DSN_PROXY_AUTH_ENA),
&MK_WSTR(ESODBC_DEF_PROXY_AUTH_ENA), /*overwrite?*/FALSE);
/* default: no trace file */
res |= assign_dsn_attr(attrs,
&MK_WSTR(ESODBC_DSN_TRACE_ENABLED),
&MK_WSTR(ESODBC_DEF_TRACE_ENABLED), /*overwrite?*/FALSE);
res |= assign_dsn_attr(attrs,
&MK_WSTR(ESODBC_DSN_TRACE_LEVEL),
&MK_WSTR(ESODBC_DEF_TRACE_LEVEL), /*overwrite?*/FALSE);
assert(0 <= res);
}
int validate_dsn(esodbc_dsn_attrs_st *attrs, const wchar_t *dsn_str,
wchar_t *err_out, size_t eo_max, BOOL on_connect)
{
int ret;
esodbc_dbc_st dbc;
if (! dsn_str) {
ERR("invalid NULL DSN string received.");
/* internal error, no user-relevant message */
return ESODBC_DSN_ISNULL_ERROR;
}
#ifdef ESODBC_DSN_API_WITH_00_LIST
/* this won't be "complete" if using 00-list */
#ifndef NDEBUG /* don't print the PWD */
DBG("received DSN string starting with: `" LWPD "`.", dsn_str);
#endif /* NDEBUG */
if (! parse_00_list(attrs, (SQLWCHAR *)dsn_str)) {
#else
#ifndef NDEBUG /* don't print the PWD */
DBG("received DSN string: `" LWPD "`.", dsn_str);
#endif /* NDEBUG */
if (! parse_connection_string(attrs, (SQLWCHAR *)dsn_str, SQL_NTS)) {
#endif /* ESODBC_DSN_API_WITH_00_LIST */
ERR("failed to parse received DSN string.");
swprintf(err_out, eo_max, L"DSN string parsing error.");
return ESODBC_DSN_INVALID_ERROR;
}
/*
* check on the minimum DSN set requirements
*/
if (! (attrs->server.cnt | attrs->cloud_id.cnt)) {
ERR("received empty server name and cloud ID");
swprintf(err_out, eo_max,
L"Server hostname and Cloud ID cannot be both empty.");
return ESODBC_DSN_INVALID_ERROR;
}
if (!on_connect && !attrs->dsn.cnt) {
ERR("received empty DSN name");
swprintf(err_out, eo_max, L"DSN name cannot be empty.");
return ESODBC_DSN_INVALID_ERROR;
}
/* fill in whatever's missing */
assign_dsn_defaults(attrs);
init_dbc(&dbc, NULL);
ret = on_connect ? do_connect(&dbc, attrs) : config_dbc(&dbc, attrs);
if (! SQL_SUCCEEDED(ret)) {
ret = EsSQLGetDiagFieldW(SQL_HANDLE_DBC, &dbc, /*rec#*/1,
SQL_DIAG_MESSAGE_TEXT, err_out, (SQLSMALLINT)eo_max,
/*written len*/NULL/*err_out is 0-term'd*/);
/* function should not fail with given params. */
assert(SQL_SUCCEEDED(ret));
ERR("test DBC %s failed: " LWPD ".",
on_connect ? "connection" : "configuration", err_out);
ret = ESODBC_DSN_GENERIC_ERROR;
} else {
ret = ESODBC_DSN_NO_ERROR; // 0
}
cleanup_dbc(&dbc);
return ret;
}
static int test_connect(void *arg, const wchar_t *dsn_str,
wchar_t *err_out, size_t eo_max, unsigned int _)
{
esodbc_dsn_attrs_st attrs;
assert(! arg); /* change santinel */
init_dsn_attrs(&attrs);
return validate_dsn(&attrs, dsn_str, err_out, eo_max, /*on conn*/TRUE);
}
/*
* Return:
* < 0: error
* ==0: user cancels
* > 0: success
*/
int prompt_user_config(HWND hwnd, BOOL on_conn, esodbc_dsn_attrs_st *attrs,
driver_callback_ft save_cb)
{
int ret;
wchar_t dsn_str[sizeof(attrs->buff)/sizeof(*attrs->buff)];
#if 0
if (write_00_list(attrs, (SQLWCHAR *)dsn_str,
sizeof(dsn_str)/sizeof(*dsn_str)) <= 0) {
#else
if (write_connection_string(attrs, (SQLWCHAR *)dsn_str,
sizeof(dsn_str)/sizeof(*dsn_str)) <= 0) {
#endif
ERR("failed to serialize attributes into a DSN string.");
return FALSE;
}
ret = EsOdbcDsnEdit(hwnd, on_conn, dsn_str, &test_connect, NULL,
save_cb, attrs);
DBG("DSN editor returned: %d.", ret);
if (ret < 0) {
ERR("failed to bring up the GUI; code:%d.", ret);
}
return ret;
}
/* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : */