driver/setup.c (246 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 "defs.h"
#include "tracing.h"
#include "log.h"
#include "info.h"
#include "dsn.h"
#include "connect.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);
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));
init_dbc(&dbc, NULL);
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;
TRACE7(_IN, NULL, "phwwphp", 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);
}
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, NULL, "dphwwpht", ret, hwndParent, fRequest, lpszDriver,
lpszArgs, lpszMsg, cbMsgMax, pcbMsgOut);
return ret;
# undef _DSN_END_MARKER
}
static int save_dsn_cb(void *arg, const wchar_t *dsn_str,
wchar_t *err_out, size_t eo_max, unsigned int flags)
{
size_t cnt;
int res;
esodbc_dsn_attrs_st attrs;
BOOL remove_old;
esodbc_dsn_attrs_st *old_attrs = (esodbc_dsn_attrs_st *)arg;
wstr_st old_dsn = old_attrs->dsn;
init_dsn_attrs(&attrs);
res = validate_dsn(&attrs, dsn_str, err_out, eo_max, /*connect?*/FALSE);
if (res < 0) {
return res;
}
/* There are the following cases possible:
* - new DSN, name not yet used;
* - new DSN, name already used;
* - old DSN renamed to a name not yet used;
* - old DSN renamed to a name already used */
/* is it a brand new DSN name or has the DSN name changed? */
DBG("old DSN name: `" LWPDL "`, new DSN name: `" LWPDL "`.",
LWSTR(&old_dsn), LWSTR(&attrs.dsn));
if (! EQ_CASE_WSTR(&old_dsn, &attrs.dsn)) { /* new DSN or name changed */
/* check if DSN name already exists */
res = system_dsn_exists(&attrs.dsn);
if (res < 0) {
cnt = copy_installer_errors(err_out, eo_max);
ERR("failed to check if DSN `" LWPDL "` already exists: "
LWPDL ".", LWSTR(&attrs.dsn), cnt, err_out);
return ESODBC_DSN_GENERIC_ERROR;
} else if (res) { /* name already in use */
DBG("overwrite confirmed? %s!", flags & ESODBC_DSN_OVERWRITE_FLAG
? "yes" : "no");
if (! (flags & ESODBC_DSN_OVERWRITE_FLAG)) {
return ESODBC_DSN_EXISTS_ERROR;
} else {
/* need to delete old entry now to make sure no attribute set
* in old one persists in new one */
if (! SQLRemoveDSNFromIniW(attrs.dsn.str)) {
cnt = copy_installer_errors(err_out, eo_max);
ERR("failed to remove old DSN with same name` " LWPDL "`:"
" " LWPDL ".", LWSTR(&old_dsn), cnt, err_out);
} else {
DBG("removed DSN to be overwritten.");
}
}
} else { /* name not yet used */
/* new DSN to be added: check name validity */
if (! SQLValidDSNW(attrs.dsn.str)) {
SQLPostInstallerError(ODBC_ERROR_INVALID_DSN, NULL);
ERR("invalid DSN value `" LWPDL "`.", LWSTR(&attrs.dsn));
return ESODBC_DSN_NAME_INVALID_ERROR;
} else {
INFO("creating new DSN `" LWPDL "` for driver ` " LWPDL " `.",
LWSTR(&attrs.dsn), LWSTR(&attrs.driver));
}
}
/* create new entry for the new DSN */
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));
cnt = copy_installer_errors(err_out, eo_max);
return ESODBC_DSN_GENERIC_ERROR;
}
/* if an old DSN exists, it'll need to be deleted */
remove_old = !!old_dsn.cnt;
/* a new entry is created, force writing all new values */
old_attrs = NULL;
} else {
remove_old = FALSE;
}
/* update/create the DSN with user values */
if (! write_system_dsn(&attrs, old_attrs)) {
cnt = copy_installer_errors(err_out, eo_max);
ERR("failed to add DSN to the system: " LWPDL ".", cnt, err_out);
return ESODBC_DSN_GENERIC_ERROR;
}
/* only remove old if new is succesfully created */
if (remove_old) {
assert(old_dsn.cnt);
if (! SQLRemoveDSNFromIniW(old_dsn.str)) {
cnt = copy_installer_errors(err_out, eo_max);
ERR("failed to remove old DSN ` " LWPDL "`: " LWPDL ".",
LWSTR(&old_dsn), cnt, err_out);
} else {
DBG("removed now renamed DSN `" LWPDL "`.", LWSTR(&old_dsn));
}
}
return 0;
}
BOOL SQL_API ConfigDSNW(
HWND hwndParent,
WORD fRequest,
LPCWSTR lpszDriver,
LPCWSTR lpszAttributes)
{
esodbc_dsn_attrs_st attrs;
wstr_st driver;
int res;
DWORD ierror = 0;
#ifndef NDEBUG /* don't print the PWD */
const char *fmt_in = "phww";
const char *fmt_out = "dphww";
#else /* NDEBUG */
const char *fmt_in = "phwp";
const char *fmt_out = "dphwp";
#endif /* NDEBUG */
TRACE4(_IN, NULL, fmt_in, hwndParent, fRequest, lpszDriver,
lpszAttributes);
/* 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)
};
/*
* If there's a DSN in received attributes, load the config from the
* registry.
*/
init_dsn_attrs(&attrs);
/* The list - as received by ConfigDSN() - only contains the 'DSN'
* keyword, though it could contain other attributes too. These are going
* to be taken into account, but possibly overwritten by registry entries
* (which theoretically should be the same anyways). */
if (! parse_00_list(&attrs, (SQLWCHAR *)lpszAttributes)) {
ERR("failed to parse doubly null-terminated attributes "
"list `" LWPD "`.", (SQLWCHAR *)lpszAttributes);
return FALSE;
}
/* ConfigDSN() requirement */
if (attrs.driver.cnt) {
ERR("function can not accept '" ESODBC_DSN_DRIVER "' keyword.");
return FALSE;
}
if (attrs.dsn.cnt) {
res = load_system_dsn(&attrs, /*overwrite?*/TRUE);
if (res <= 0) {
if (res < 0) {
log_installer_err();
}
ERR("failed to load system DSN for driver ` " LWPD " ` and "
"attributes `" LWPDL "`.", LWSTR(&driver), lpszAttributes);
return FALSE;
}
}
res = assign_dsn_attr(&attrs, &MK_WSTR(ESODBC_DSN_DRIVER), &driver,
/*overwrite?*/FALSE);
assert(0 < res);
switch (fRequest) {
case ODBC_CONFIG_DSN:
case ODBC_ADD_DSN:
res = prompt_user_config(hwndParent, FALSE, &attrs, save_dsn_cb);
if (res < 0) {
ierror = ODBC_ERROR_REQUEST_FAILED;
ERR("failed getting user values.");
}
break;
case ODBC_REMOVE_DSN:
if (! SQLRemoveDSNFromIniW(attrs.dsn.str)) {
ierror = ODBC_ERROR_REQUEST_FAILED;
ERR("failed to remove driver ` " LWPD " ` with "
"attributes `" LWPDL "`.", LWSTR(&driver), lpszAttributes);
} else {
INFO("removed DSN `" LWPDL "` from the system.",
LWSTR(&attrs.dsn));
}
break;
default:
ERR("unexpected configuration request type: %hd.", fRequest);
ierror = ODBC_ERROR_INVALID_REQUEST_TYPE;
}
if (ierror) {
SQLPostInstallerError(ODBC_ERROR_REQUEST_FAILED, NULL);
}
TRACE5(_OUT, NULL, fmt_out, ierror, hwndParent, fRequest, lpszDriver,
lpszAttributes);
return ierror == 0;
}
/* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : */