某个客户现场部署的数据库是Oracle的,历史遗留原因,Oracle密码在配置文件里是明文的,客户要求修改为密文,修改后交由客户验证,客户反馈修改密码后,终端上报审计信息插入Oracle时,模块崩溃了,按理说密码即使不对或者解析问题也不会崩溃
然后我们看下代码流程:
主线版本如果set之后是需要调用connect()方法去校验下密码是否能成功连接odbc
HOTLDBMgr::ins().set(m_config.mstr_username,m_config.mstr_userpasswd,m_config.mstr_dsn);
int i_con = -1;
while (--i_try_connect_times && i_con != 0)
{
i_con = HOTLDBMgr::ins().connect();
if (i_con != 0)
{
HEnvironment::Sleep(6000);
}
}
包含Oracle的分支代码只set(),没有connect()校验,至于为什么没有去connect(),历史遗留代码不太清楚,先不管这个
HString strIsDec;
ini.read_string(L"Common", L"OraclePasswdBase64", strIsDec);
if (strIsDec.is_equal(L"true"))
{
string strDecPw = HBase64::decode(str_password_oracle.get_ice_str());
str_password_oracle.make_by_ice_str(strDecPw);
}
HOTLDBMgrOracle::ins().set(str_username_oracle,str_password_oracle,str_dsn_oracle);
看下具体流程
//1 终端上报审计信息 server去入库
HOTLStreamOracle* p_stream = HOTLDBMgrOracle::ins().do_exec_sql(strSql,UNISERVER_DB_OP_BUFFER);
//2 do_exec_sql里面调用了 get_connect()
oracle::otl_connect* p_connect = get_connect(ui_id , str_sql);
//3 get_connect里面调用了get_new_connect()
oracle::otl_connect* p_connect = get_new_connect();
//4 get_new_connect里面调用了 rlogon()
oracle::otl_connect* mp_connect = 0;
HString str_login = get_login_str();
mp_connect = new oracle::otl_connect;
mp_connect->rlogon(str_login.get_str_direct().c_str()); // connect to ODBC
//5 根据堆栈和1的调用 可以知道当密码校验失败后返回的是空指针 这里的逻辑是失败后(密码校验失败,连接断开等情况)
//保存这些sql内容到文件中等后面重新尝试插入(重启或者定时执行)
//但是发现在失败后保存sql的逻辑里面又使用了空指针p_stream这种操作 *p_stream << 1;导致的崩溃
if(p_stream != NULL)
{
//执行入库
...
}
else
{
//失败保存记录
b_need_save = true;
}
if(b_need_save)
{
//保存记录
...
if (b_need_notify)
{
*p_stream << 1;
//insert_fail_record.begin() << 1; 应该使用这个
}
else
{
*p_stream << 0;
//insert_fail_record.begin() << 0;应该使用这个
}
insert_fail_record.end();
}
以上,可能是复制代码忘记改了,估计测试也没有进入到这个分支过,才遗留了这么个问题
但是密码为什么校验失败了,我们解密出来的密码事实上是正确的,这才是我们要关注的地方
//查看日志文件发现了这样的报错
[1][2023-03-22][11:01:29][T3909064448][HOTLDBMgrOracle.h ][2095][W]connect error Msg:ORA-12154: TNS:could not resolve the connect identifier specified
, STM:, State:, Var: ,take time 2 ms
//然后我们查看对应行数的代码,发现上面的第4点
HString str_login = get_login_str();
//get_login_str()中有这样的一行代码
str_login = mstr_user + L"/" + mstr_pass + L"@" + str_server_info;
这里是把账号密码用这种形式拼接起来调用rlogon()去连接odbc的 user/password@DSN
再结合客户设置的密码中包含’@‘符号 能猜出个大概了 可能是rlogon()里面处理的时候会根据这些符号进行切割
设置的密码中含有’@',导致切割出来的字符串有误 从而密码校验失败
rlogon()这个封装在最底层的接口是otl提供的,查看官网接口文档
https://otl.sourceforge.net/otl3_connect_class.htm
我们发现了这一段话
rlogon()接口非常的通用,并且提供的格式就是上述所说的 user/password@DSN ,并且如果密码中含有’@'符号,需要使用这样格式userid/pass\\@word@DSN才行
接着我们查看otl中rlogon()的实现来作为验证
不需要看实现细节,也能看出来根据’/’ '@'符号去切割的,当然官网也提供了更多的重载的接口(rlogon),并非一定要使用这种格式的接口,但是这种接口是更通用的,对于连接不同类型的数据库来说
至此,问题已经清晰。底层封装了rlogon()接口,用户密码DSN必须使用特定格式否则otl内部无法成功解析而导致连接odbc失败。密码中可以携带’@‘,但是前面需要加上’\\’
OTL_NODISCARD int rlogon(const char *connect_str, const int
#ifndef OTL_ODBC_MYSQL
auto_commit
#endif
) {
char username[256];
char passwd[256];
char tnsname[1024];
char *tnsname_ptr = nullptr;
char *c = OTL_CCAST(char *, connect_str);
char *username_ptr = username;
char *passwd_ptr = passwd;
char temp_connect_str[512];
if (extern_lda) {
extern_lda = false;
henv = OTL_SQL_NULL_HANDLE_VAL;
hdbc = OTL_SQL_NULL_HANDLE_VAL;
}
memset(username, 0, sizeof(username));
memset(passwd, 0, sizeof(passwd));
memset(tnsname, 0, sizeof(tnsname));
char *c1 = OTL_CCAST(char *, connect_str);
int oracle_format = 0;
char prev_c = ' ';
while (*c1) {
if (*c1 == '@' && prev_c != '\\') {
oracle_format = 1;
break;
}
prev_c = *c1;
++c1;
}
if (oracle_format) {
while (*c && *c != '/' && (OTL_SCAST(unsigned, username_ptr - username) <
sizeof(username) - 1)) {
*username_ptr = *c;
++c;
++username_ptr;
}
*username_ptr = 0;
if (*c == '/')
++c;
prev_c = ' ';
while (*c && !(*c == '@' && prev_c != '\\') &&
(OTL_SCAST(unsigned, passwd_ptr - passwd) < sizeof(passwd) - 1)) {
if (prev_c == '\\')
--passwd_ptr;
*passwd_ptr = *c;
prev_c = *c;
++c;
++passwd_ptr;
}
*passwd_ptr = 0;
if (*c == '@') {
++c;
tnsname_ptr = tnsname;
while (*c && (OTL_SCAST(unsigned, tnsname_ptr - tnsname) <
sizeof(tnsname) - 1)) {
*tnsname_ptr = *c;
++c;
++tnsname_ptr;
}
*tnsname_ptr = 0;
}
} else {
c1 = OTL_CCAST(char *, connect_str);
char *c2 = temp_connect_str;
while (*c1 && (OTL_SCAST(unsigned, c2 - temp_connect_str) <
sizeof(temp_connect_str) - 1)) {
*c2 = otl_to_upper(*c1);
++c1;
++c2;
}
*c2 = 0;
c1 = temp_connect_str;
char entry_name[256];
char entry_value[256];
while (*c1 && (OTL_SCAST(unsigned, c1 - temp_connect_str) <
sizeof(temp_connect_str) - 1)) {
c2 = entry_name;
while (*c1 && *c1 != '=' &&
(OTL_SCAST(unsigned, c1 - temp_connect_str) <
sizeof(temp_connect_str) - 1)) {
*c2 = *c1;
++c1;
++c2;
}
*c2 = 0;
#if defined(_MSC_VER) && (_MSC_VER == 1600)
entry_name[c2 - entry_name] = 0;
#endif
if (*c1)
++c1;
c2 = entry_value;
prev_c = ' ';
while (*c1 && *c1 != ';' && (OTL_SCAST(unsigned, c2 - entry_value) <
sizeof(entry_value) - 1)) {
if (prev_c == '\\')
--c2;
*c2 = *c1;
prev_c = *c1;
++c1;
++c2;
}
*c2 = 0;
#if defined(_MSC_VER) && (_MSC_VER == 1600)
entry_value[c2 - entry_value] = 0;
#endif
if (*c1)
++c1;
if (strcmp(entry_name, "DSN") == 0)
OTL_STRCPY_S(tnsname, sizeof(tnsname), entry_value);
if (strcmp(entry_name, "UID") == 0)
OTL_STRCPY_S(username, sizeof(username), entry_value);
if (strcmp(entry_name, "PWD") == 0)
OTL_STRCPY_S(passwd, sizeof(passwd), entry_value);
}
}
#ifndef OTL_ODBC_MYSQL
OTL_TRACE_RLOGON_ODBC(0x1, "otl_connect", "rlogon", tnsname, username,
passwd, auto_commit)
#else
OTL_TRACE_RLOGON_ODBC(0x1, "otl_connect", "rlogon", tnsname, username,
passwd, 0)
#endif
if (henv == OTL_SQL_NULL_HANDLE_VAL || hdbc == OTL_SQL_NULL_HANDLE_VAL) {
#if (ODBCVER >= 0x0300)
status = SQLAllocHandle(SQL_HANDLE_ENV, OTL_SQL_NULL_HANDLE_VAL, &henv);
#else
status = SQLAllocEnv(&henv);
#endif
if (status != SQL_SUCCESS && status != SQL_SUCCESS_WITH_INFO)
return 0;
#if (ODBCVER >= 0x0300)
status = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION,
OTL_RCAST(void *, SQL_OV_ODBC3), SQL_NTS);
if (status != SQL_SUCCESS && status != SQL_SUCCESS_WITH_INFO)
return 0;
#endif
#if (ODBCVER >= 0x0300)
status = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
#else
status = SQLAllocConnect(henv, &hdbc);
#endif
if (status != SQL_SUCCESS && status != SQL_SUCCESS_WITH_INFO)
return 0;
} else
status = SQL_SUCCESS;
#ifndef OTL_ODBC_MYSQL
#if (ODBCVER >= 0x0300)
if (auto_commit)
status = SQLSetConnectAttr(hdbc, SQL_ATTR_AUTOCOMMIT,
OTL_RCAST(SQLPOINTER, SQL_AUTOCOMMIT_ON),
SQL_IS_POINTER);
else
status = SQLSetConnectAttr(hdbc, SQL_ATTR_AUTOCOMMIT,
#if defined(OTL_ANSI_CPP_11_NULLPTR_SUPPORT)
nullptr,
#else
OTL_RCAST(SQLPOINTER, SQL_AUTOCOMMIT_OFF),
#endif
SQL_IS_POINTER);
#else
if (auto_commit)
status = SQLSetConnectOption(hdbc, SQL_AUTOCOMMIT, 1);
else
status = SQLSetConnectOption(hdbc, SQL_AUTOCOMMIT, 0);
#endif
if (status != SQL_SUCCESS && status != SQL_SUCCESS_WITH_INFO)
return 0;
#endif
#if (ODBCVER >= 0x0300)
if (timeout > 0)
status =
SQLSetConnectAttr(hdbc, SQL_ATTR_LOGIN_TIMEOUT,
OTL_RCAST(void *, OTL_SCAST(size_t, timeout)), 0);
#else
if (timeout > 0)
status = SQLSetConnectOption(hdbc,
SQL_LOGIN_TIMEOUT,
OTL_SCAST(size_t,timeout));
#endif
if (status != SQL_SUCCESS && status != SQL_SUCCESS_WITH_INFO)
return 0;
#if defined(OTL_DB2_CLI)
status = SQLSetConnectAttr(hdbc, SQL_ATTR_LONGDATA_COMPAT,
OTL_RCAST(SQLPOINTER, SQL_LD_COMPAT_YES),
SQL_IS_INTEGER);
if (status != SQL_SUCCESS && status != SQL_SUCCESS_WITH_INFO)
return 0;
#endif
#if defined(OTL_ENABLE_MSSQL_MARS)
#if !defined(OTL_DB2_CLI) && (ODBCVER >= 0x0300)
status = SQLSetConnectAttr(hdbc, OTL_SQL_COPT_SS_MARS_ENABLED,
OTL_RCAST(SQLPOINTER, OTL_SQL_MARS_ENABLED_YES),
SQL_IS_UINTEGER);
if (status != SQL_SUCCESS_WITH_INFO && status != SQL_SUCCESS)
return 0;
#endif
#endif
if (oracle_format) {
#if defined(OTL_ODBC_zOS)
if (tnsname[0] == 0 && username[0] == 0 && passwd[0] == 0) {
status = SQLConnect(hdbc, 0L, SQL_NTS, 0L, SQL_NTS, 0L, SQL_NTS);
logoff_commit = false;
} else
status = SQLConnect(hdbc, OTL_RCAST(unsigned char *, tnsname), SQL_NTS,
OTL_RCAST(unsigned char *, username), SQL_NTS,
OTL_RCAST(unsigned char *, passwd), SQL_NTS);
#else
#if defined(UNICODE) || defined(_UNICODE)
{
SQLWCHAR *temp_tnsname = new SQLWCHAR[strlen(tnsname) + 1];
SQLWCHAR *temp_username = new SQLWCHAR[strlen(username) + 1];
SQLWCHAR *temp_passwd = new SQLWCHAR[strlen(passwd) + 1];
otl_convert_char_to_SQLWCHAR_2(temp_tnsname,
OTL_RCAST(unsigned char *, tnsname));
otl_convert_char_to_SQLWCHAR_2(temp_username,
OTL_RCAST(unsigned char *, username));
otl_convert_char_to_SQLWCHAR_2(temp_passwd,
OTL_RCAST(unsigned char *, passwd));
status = SQLConnect(hdbc, temp_tnsname, SQL_NTS, temp_username, SQL_NTS,
temp_passwd, SQL_NTS);
delete[] temp_tnsname;
delete[] temp_username;
delete[] temp_passwd;
}
#else
status = SQLConnect(hdbc, OTL_RCAST(unsigned char *, tnsname), SQL_NTS,
OTL_RCAST(unsigned char *, username), SQL_NTS,
OTL_RCAST(unsigned char *, passwd), SQL_NTS);
#endif
#endif
} else {
char *tc2 = temp_connect_str;
const char *tc1 = connect_str;
prev_c = ' ';
while (*tc1 && (OTL_SCAST(unsigned, tc2 - temp_connect_str) <
sizeof(temp_connect_str) - 1)) {
if (*tc1 == '@' && prev_c == '\\')
--tc2;
*tc2 = *tc1;
prev_c = *tc1;
++tc1;
++tc2;
}
*tc2 = 0;
#if defined(_MSC_VER) && (_MSC_VER == 1600)
temp_connect_str[tc2 - temp_connect_str] = 0;
#endif
SQLSMALLINT out_len = 0;
#if (defined(UNICODE) || defined(_UNICODE))
{
size_t len = strlen(temp_connect_str);
SQLWCHAR *temp_connect_str2 = new SQLWCHAR[len + 1];
SQLWCHAR out_str[2048];
otl_convert_char_to_SQLWCHAR_2(
temp_connect_str2, OTL_RCAST(unsigned char *, temp_connect_str));
status = SQLDriverConnect(
hdbc, nullptr, temp_connect_str2, OTL_SCAST(short, len), out_str,
OTL_SCAST(OTL_SQLSMALLINT, sizeof(out_str) / sizeof(SQLWCHAR)),
&out_len, SQL_DRIVER_NOPROMPT);
delete[] temp_connect_str2;
}
#else
SQLCHAR out_str[2048];
status = SQLDriverConnect(
hdbc, nullptr,
OTL_RCAST(SQLCHAR *, OTL_CCAST(char *, temp_connect_str)),
OTL_SCAST(short, strlen(temp_connect_str)), out_str,
OTL_SCAST(SQLSMALLINT, sizeof(out_str)), &out_len,
SQL_DRIVER_NOPROMPT);
#endif
}
if (status == SQL_SUCCESS_WITH_INFO || status == SQL_SUCCESS)
return 1;
else
return 0;
}