otl rlogon(...)连接失败的问题分析
某个客户现场部署的数据库是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_MYSQLauto_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;
#endifif (*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;
#endifif (*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_MYSQLOTL_TRACE_RLOGON_ODBC(0x1, "otl_connect", "rlogon", tnsname, username,passwd, auto_commit)
#elseOTL_TRACE_RLOGON_ODBC(0x1, "otl_connect", "rlogon", tnsname, username,passwd, 0)
#endifif (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);
#elsestatus = SQLAllocEnv(&henv);
#endifif (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);
#elsestatus = SQLAllocConnect(henv, &hdbc);
#endifif (status != SQL_SUCCESS && status != SQL_SUCCESS_WITH_INFO)return 0;} elsestatus = 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);elsestatus = SQLSetConnectAttr(hdbc, SQL_ATTR_AUTOCOMMIT,
#if defined(OTL_ANSI_CPP_11_NULLPTR_SUPPORT)nullptr,
#elseOTL_RCAST(SQLPOINTER, SQL_AUTOCOMMIT_OFF),
#endifSQL_IS_POINTER);
#elseif (auto_commit)status = SQLSetConnectOption(hdbc, SQL_AUTOCOMMIT, 1);elsestatus = SQLSetConnectOption(hdbc, SQL_AUTOCOMMIT, 0);
#endifif (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);
#elseif (timeout > 0)status = SQLSetConnectOption(hdbc, SQL_LOGIN_TIMEOUT, OTL_SCAST(size_t,timeout));
#endifif (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
#endifif (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;} elsestatus = 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;}
#elsestatus = 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;
#endifSQLSMALLINT 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;}
#elseSQLCHAR 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;elsereturn 0;}