diff --git a/docs/en/14-reference/09-error-code.md b/docs/en/14-reference/09-error-code.md index 58e6cd5cea0..d56ad830a30 100644 --- a/docs/en/14-reference/09-error-code.md +++ b/docs/en/14-reference/09-error-code.md @@ -138,7 +138,8 @@ Below are the business error codes for each module. | 0x8000022E | No available execution node | No available query execution node | Check the current query policy configuration, ensure available Qnode if needed | | 0x8000022F | Table is not a supertable | Table name in the statement is not a supertable | Check if the table name used in the statement is a supertable | | 0x80000230 | Stmt cache error | STMT/STMT2 internal cache error | Preserve the scene and logs, report issue on GitHub | -| 0x80000231 | Tsc internal error | TSC internal error | Preserve the scene and logs, report issue on GitHub | +| 0x80000238 | Invalid TOTP code | Invalid TOTP code | Check and enter the correct TOTP code | +| 0x800002FF | Tsc internal error | TSC internal error | Preserve the scene and logs, report issue on GitHub | #### mnode @@ -179,6 +180,7 @@ Below are the business error codes for each module. | 0x80000355 | Too many users | (Enterprise only) Exceeding user limit | Adjust configuration | | 0x80000357 | Authentication failure | Incorrect password | Confirm if the operation is correct | | 0x80000358 | User not available | User does not exist | Confirm if the operation is correct | +| 0x8000035B | Wrong TOTP code | TOTP code not provided or wrong TOTP code | Check and enter the correct TOTP code | | 0x80000360 | STable already exists | Internal error | Report issue | | 0x80000361 | STable not exist | Internal error | Report issue | | 0x80000364 | Too many tags | Too many tags | Cannot be modified, code-level restriction | @@ -533,19 +535,19 @@ Below are the business error codes for each module. | 0x8000268A | Cols function's first param must be a select function that output a single row | The first parameter of the cols function should be a selection function | Check and correct the SQL statement | | 0x8000268B | Invalid using alias for cols function | Illegal cols function alias | Check and correct the SQL statement | | 0x8000268C | Join primary key col must be timestamp type | Join primary key data type error | Check and correct the SQL statement | -| 0x8000268D | Invalid virtual table's ref column | Create/Update Virtual table using incorrect data source column | Check and correct the SQL statement | -| 0x8000268E | Invalid table type | Incorrect Table type | Check and correct the SQL statement | -| 0x8000268F | Invalid ref column type | Virtual table's column type and data source column's type are different | Check and correct the SQL statement | -| 0x80002690 | Create child table using virtual super table | Create non-virtual child table using virtual super table | Check and correct the SQL statement | -| 0x80002696 | Invalid sliding offset | Invalid sliding offset | Check and correct the SQL statement | -| 0x80002697 | Invalid interval offset | Invalid interval offset | Check and correct the SQL statement | -| 0x80002698 | Invalid extend value | Invalid extend value | Check and correct the SQL statement | -| 0x80002699 | Algorithm ID too long, max length is 63 character | Invalid algorithm id value | Check and correct the SQL statement | -| 0x8000269A | Algorithm name too long, max length is 63 character | Invalid algorithm name value | Check and correct the SQL statement | -| 0x8000269B | Algorithm description too long, max length is 127 character | Invalid algorithm description value | Check and correct the SQL statement | -| 0x8000269C | Algorithm type too long, max length is 63 character | Invalid algorithm type value | Check and correct the SQL statement | -| 0x8000269D | Algorithm OpenSSL name too long, max length is 63 character | Invalid algorithm OpenSSL name value | Check and correct the SQL statement | -| 0x8000269E | Option duplicated | Option is only allowed to appear once but appeared twice or more | Check and correct the SQL statement | +| 0x8000268D | Invalid virtual table's ref column | Create/Update Virtual table using incorrect data source column | Check and correct the SQL statement | +| 0x8000268E | Invalid table type | Incorrect Table type | Check and correct the SQL statement | +| 0x8000268F | Invalid ref column type | Virtual table's column type and data source column's type are different | Check and correct the SQL statement | +| 0x80002690 | Create child table using virtual super table | Create non-virtual child table using virtual super table | Check and correct the SQL statement | +| 0x80002696 | Invalid sliding offset | Invalid sliding offset | Check and correct the SQL statement | +| 0x80002697 | Invalid interval offset | Invalid interval offset | Check and correct the SQL statement | +| 0x80002698 | Invalid extend value | Invalid extend value | Check and correct the SQL statement | +| 0x80002699 | Algorithm ID too long, max length is 63 character | Invalid algorithm id value | Check and correct the SQL statement | +| 0x8000269A | Algorithm name too long, max length is 63 character | Invalid algorithm name value | Check and correct the SQL statement | +| 0x8000269B | Algorithm description too long, max length is 127 character | Invalid algorithm description value | Check and correct the SQL statement | +| 0x8000269C | Algorithm type too long, max length is 63 character | Invalid algorithm type value | Check and correct the SQL statement | +| 0x8000269D | Algorithm OpenSSL name too long, max length is 63 character | Invalid algorithm OpenSSL name value | Check and correct the SQL statement | +| 0x8000269E | Option duplicated | Option is only allowed to appear once but appeared twice or more | Check and correct the SQL statement | | 0x8000269F | Invalid option value | Invalid option value | Check and correct the SQL statement | | 0x800026A0 | Option value too long | Option value too long | Check and correct the SQL statement | | 0x800026A1 | Option value too short | Option value too short | Check and correct the SQL statement | @@ -555,10 +557,10 @@ Below are the business error codes for each module. | 0x80002700 | Planner internal error | Internal error in planner | Preserve the scene and logs, report issue on GitHub | | 0x80002701 | Expect ts equal | JOIN condition validation failed | Preserve the scene and logs, report issue on GitHub | | 0x80002702 | Cross join not support | CROSS JOIN not supported | Check and correct the SQL statement | -| 0x80002704 | Planner slot key not found | Planner cannot find slotId during making physic plan | Preserve the scene and logs, report issue on GitHub | -| 0x80002705 | Planner invalid table type | Planner get invalid table type | Preserve the scene and logs, report issue on GitHub | -| 0x80002706 | Planner invalid query control plan type | Planner get invalid query control plan type during making physic plan | Preserve the scene and logs, report issue on GitHub | -| 0x80002707 | Planner invalid window type | Planner get invalid window type during making physic plan | Preserve the scene and logs, report issue on GitHub | +| 0x80002704 | Planner slot key not found | Planner cannot find slotId during making physic plan | Preserve the scene and logs, report issue on GitHub | +| 0x80002705 | Planner invalid table type | Planner get invalid table type | Preserve the scene and logs, report issue on GitHub | +| 0x80002706 | Planner invalid query control plan type | Planner get invalid query control plan type during making physic plan | Preserve the scene and logs, report issue on GitHub | +| 0x80002707 | Planner invalid window type | Planner get invalid window type during making physic plan | Preserve the scene and logs, report issue on GitHub | #### function diff --git a/docs/zh/14-reference/09-error-code.md b/docs/zh/14-reference/09-error-code.md index d50f2cf58e3..d72d6baf75c 100644 --- a/docs/zh/14-reference/09-error-code.md +++ b/docs/zh/14-reference/09-error-code.md @@ -136,7 +136,8 @@ TSDB 错误码包括 taosc 客户端和服务端,所有语言的连接器无 | 0x8000022E | No available execution node | 没有可用的查询执行节点 | 检查当前 query policy 配置,如果需要有 Qnode 参与确保系统中存在可用的 Qnode 节点 | | 0x8000022F | Table is not a super table | 当前语句中的表名不是超级表 | 检查当前语句中所用表名是否是超级表 | | 0x80000230 | Stmt cache error | STMT/STMT2 内部缓存出错 | 保留现场和日志,github 上报 issue | -| 0x80000231 | Tsc internal error | TSC 内部错误 | 保留现场和日志,github 上报 issue | +| 0x80000238 | Invalid TOTP code | 输入的 TOTP 验证码格式错误 | 检查并重新输入正确的 TOTP 验证码 | +| 0x800002FF | Tsc internal error | TSC 内部错误 | 保留现场和日志,github 上报 issue | #### mnode @@ -164,7 +165,7 @@ TSDB 错误码包括 taosc 客户端和服务端,所有语言的连接器无 | 0x80000332 | Vgroup does not exist | 内部错误 | 上报 issue | | 0x80000333 | Cannot drop mnode which is leader | 操作节点为 leader | 确认操作是否正确 | | 0x80000334 | Out of dnodes | dnode 节点数量不够 | 增加 dnode 节点 | -| 0x80000335 | Cluster cfg inconsistent | 配置不一致 | 检查 dnode 节点与 mnode 节点配置是否一致。检查方式:1.节点启动时,在日志中输出 2.使用 show variables | +| 0x80000335 | Cluster cfg inconsistent | 配置不一致 | 检查 dnode 节点与 mnode 节点配置是否一致。检查方式:1.节点启动时,在日志中输出 2.使用 show variables | | 0x8000033B | Cluster id not match | 节点配置数据不一致 | 检查各节点 data/dnode/dnodes.json 文件中的 clusterid | | 0x80000340 | Account already exists | (仅企业版)内部错误 | 上报 issue | | 0x80000342 | Invalid account options | (仅企业版)该操作不支持 | 确认操作是否正确 | @@ -177,6 +178,7 @@ TSDB 错误码包括 taosc 客户端和服务端,所有语言的连接器无 | 0x80000355 | Too many users | (仅企业版)用户数量超限 | 调整配置 | | 0x80000357 | Authentication failure | 密码不正确 | 确认操作是否正确 | | 0x80000358 | User not available | 用户不存在 | 确认操作是否正确 | +| 0x8000035B | Wrong TOTP code | 未提供或提供了错误的 TOTP 验证码 | 检查并输入正确的 TOTP 验证码 | | 0x80000360 | STable already exists | 内部错误 | 上报 issue | | 0x80000361 | STable not exist | 内部错误 | 上报 issue | | 0x80000364 | Too many tags | tag 数量太多 | 不能修改,代码级别限制 | @@ -481,7 +483,7 @@ TSDB 错误码包括 taosc 客户端和服务端,所有语言的连接器无 | 0x80002635 | Incorrect TIMESTAMP value | 主键时间戳列值非法 | 检查并修正 SQL 语句 | | 0x80002637 | soffset/offset can not be less than 0 | soffset/offset 值非法 | 检查并修正 SQL 语句 | | 0x80002638 | slimit/soffset only available for PARTITION/GROUP BY query | slimit/soffset 只支持 PARTITION BY/GROUP BY 语句 | 检查并修正 SQL 语句 | -| 0x80002639 | Invalid topic query | 不支持的 TOPIC 查询语法 | 检查并修正 SQL 语句 | +| 0x80002639 | Invalid topic query | 不支持的 TOPIC 查询语法 | 检查并修正 SQL 语句 | | 0x8000263A | Cannot drop super table in batch | 不支持批量删除超级表 | 检查并修正 SQL 语句 | | 0x8000263B | Start(end) time of query range required or time range too large | 窗口个数超出限制 | 检查并修正 SQL 语句 | | 0x8000263C | Duplicated column names | 列名称重复 | 检查并修正 SQL 语句 | @@ -538,12 +540,12 @@ TSDB 错误码包括 taosc 客户端和服务端,所有语言的连接器无 | 0x80002696 | Invalid sliding offset | sliding 窗口偏移量非法 | 检查并修正 SQL 语句 | | 0x80002697 | Invalid interval offset | interval 窗口偏移量非法 | 检查并修正 SQL 语句 | | 0x80002698 | Invalid extend value | extend 参数非法 | 检查并修正 SQL 语句 | -| 0x80002699 | Algorithm ID too long, max length is 63 character | Algorithm ID 参数非法 | 检查并修正 SQL 语句 | -| 0x8000269A | Algorithm name too long, max length is 63 character | Algorithm name 参数非法 | 检查并修正 SQL 语句 | -| 0x8000269B | Algorithm description too long, max length is 127 character | Algorithm description 参数非法 | 检查并修正 SQL 语句 | -| 0x8000269C | Algorithm type too long, max length is 63 character | Algorithm type 参数非法 | 检查并修正 SQL 语句 | -| 0x8000269D | Algorithm OpenSSL name too long, max length is 63 character | Algorithm OpenSSL name 参数非法 | 检查并修正 SQL 语句 | -| 0x8000269E | Option duplicated | 只允许出现一次的选项出现了多次 | 检查并修正 SQL 语句 | +| 0x80002699 | Algorithm ID too long, max length is 63 character | Algorithm ID 参数非法 | 检查并修正 SQL 语句 | +| 0x8000269A | Algorithm name too long, max length is 63 character | Algorithm name 参数非法 | 检查并修正 SQL 语句 | +| 0x8000269B | Algorithm description too long, max length is 127 character | Algorithm description 参数非法 | 检查并修正 SQL 语句 | +| 0x8000269C | Algorithm type too long, max length is 63 character | Algorithm type 参数非法 | 检查并修正 SQL 语句 | +| 0x8000269D | Algorithm OpenSSL name too long, max length is 63 character | Algorithm OpenSSL name 参数非法 | 检查并修正 SQL 语句 | +| 0x8000269E | Option duplicated | 只允许出现一次的选项出现了多次 | 检查并修正 SQL 语句 | | 0x8000269F | Invalid option value | 选项的值非法 | 检查并修正 SQL 语句 | | 0x800026A0 | Option value too long | 选项的值太长 | 检查并修正 SQL 语句 | | 0x800026A1 | Option value too short | 选项的值太短 | 检查并修正 SQL 语句 | @@ -556,7 +558,7 @@ TSDB 错误码包括 taosc 客户端和服务端,所有语言的连接器无 | 0x80002704 | Planner slot key not found | 生成物理计划时查找不到 slotId | 保留现场和日志,github 上报 issue | | 0x80002705 | Planner invalid table type | 计划器生成计划时得到了错误的表类型 | 保留现场和日志,github 上报 issue | | 0x80002706 | Planner invalid query control plan type | 计划器生成 dynamic query control 计划时得到的类型不正确 | 保留现场和日志,github 上报 issue | -| 0x80002707 | Planner invalid window type | 计划器生成物理计划时得到了错误的窗口类型 | 保留现场和日志,github 上报 issue | +| 0x80002707 | Planner invalid window type | 计划器生成物理计划时得到了错误的窗口类型 | 保留现场和日志,github 上报 issue | #### function diff --git a/include/client/taos.h b/include/client/taos.h index 9eb170891fc..ca486544811 100644 --- a/include/client/taos.h +++ b/include/client/taos.h @@ -204,6 +204,9 @@ DLL_EXPORT TAOS *taos_connect_auth(const char *ip, const char *user, const char * @return TAOS* connection handle on success; NULL if unsupported or on error */ DLL_EXPORT TAOS *taos_connect_with_dsn(const char *dsn); +DLL_EXPORT TAOS *taos_connect_totp(const char *ip, const char *user, const char *pass, const char* totp, const char *db, uint16_t port); +DLL_EXPORT int taos_connect_test(const char *ip, const char *user, const char *pass, const char* totp, const char *db, uint16_t port); +DLL_EXPORT TAOS *taos_connect_token(const char *ip, const char *token, const char *db, uint16_t port); DLL_EXPORT void taos_close(TAOS *taos); DLL_EXPORT const char *taos_data_type(int type); diff --git a/include/common/tmsg.h b/include/common/tmsg.h index 60519c1d035..4eb9b0e98b1 100644 --- a/include/common/tmsg.h +++ b/include/common/tmsg.h @@ -131,6 +131,7 @@ enum { CONN_TYPE__QUERY = 1, CONN_TYPE__TMQ, CONN_TYPE__UDFD, + CONN_TYPE__AUTH_TEST, // only for test authentication CONN_TYPE__MAX, }; @@ -1277,6 +1278,7 @@ typedef struct { char passwd[TSDB_PASSWORD_LEN]; int64_t startTime; char sVer[TSDB_VERSION_LEN]; + int32_t totpCode; } SConnectReq; int32_t tSerializeSConnectReq(void* buf, int32_t bufLen, SConnectReq* pReq); @@ -1290,7 +1292,6 @@ typedef struct { int8_t superUser; int8_t sysInfo; int8_t connType; - int8_t mustChangePass; SEpSet epSet; int32_t svrTimestamp; int32_t passVer; diff --git a/include/libs/function/functionMgt.h b/include/libs/function/functionMgt.h index cc038ff8179..c0d8953e1bf 100644 --- a/include/libs/function/functionMgt.h +++ b/include/libs/function/functionMgt.h @@ -126,7 +126,8 @@ typedef enum EFunctionType { FUNCTION_TYPE_FIND_IN_SET, FUNCTION_TYPE_LIKE_IN_SET, FUNCTION_TYPE_REGEXP_IN_SET, - + FUNCTION_TYPE_GENERATE_TOTP_SECRET, + FUNCTION_TYPE_GENERATE_TOTP_CODE, // conversion function FUNCTION_TYPE_CAST = 2000, diff --git a/include/libs/scalar/scalar.h b/include/libs/scalar/scalar.h index b06d12c86c9..7d7e9583a7c 100644 --- a/include/libs/scalar/scalar.h +++ b/include/libs/scalar/scalar.h @@ -103,6 +103,8 @@ int32_t crc32Function(SScalarParam* pInput, int32_t inputNum, SScalarParam* pOut int32_t findInSetFunction(SScalarParam* pInput, int32_t inputNum, SScalarParam* pOutput); int32_t likeInSetFunction(SScalarParam* pInput, int32_t inputNum, SScalarParam* pOutput); int32_t regexpInSetFunction(SScalarParam* pInput, int32_t inputNum, SScalarParam* pOutput); +int32_t generateTotpSecretFunction(SScalarParam* pInput, int32_t inputNum, SScalarParam* pOutput); +int32_t generateTotpCodeFunction(SScalarParam* pInput, int32_t inputNum, SScalarParam* pOutput); /* Conversion functions */ int32_t castFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput); diff --git a/include/util/taoserror.h b/include/util/taoserror.h index dbf8a78e421..ea8d4726a96 100644 --- a/include/util/taoserror.h +++ b/include/util/taoserror.h @@ -222,6 +222,7 @@ int32_t taosGetErrSize(); #define TSDB_CODE_TSC_FAIL_GENERATE_JSON TAOS_DEF_ERROR_CODE(0, 0x0235) #define TSDB_CODE_TSC_STMT_BIND_NUMBER_ERROR TAOS_DEF_ERROR_CODE(0, 0x0236) #define TSDB_CODE_NOT_SUPPORTTED_IN_WINDOWS TAOS_DEF_ERROR_CODE(0, 0x0237) +#define TSDB_CODE_TSC_INVALID_TOTP_CODE TAOS_DEF_ERROR_CODE(0, 0x0238) #define TSDB_CODE_TSC_INTERNAL_ERROR TAOS_DEF_ERROR_CODE(0, 0x02FF) // mnode-common @@ -304,7 +305,7 @@ int32_t taosGetErrSize(); #define TSDB_CODE_MND_USER_NOT_AVAILABLE TAOS_DEF_ERROR_CODE(0, 0x0358) #define TSDB_CODE_MND_PRIVILEDGE_EXIST TAOS_DEF_ERROR_CODE(0, 0x0359) #define TSDB_CODE_MND_USER_PASSWORD_REUSE TAOS_DEF_ERROR_CODE(0, 0x035A) -// #define TSDB_CODE_MND_USER_TIME_RANGE_CONFLICT TAOS_DEF_ERROR_CODE(0, 0x035B) +#define TSDB_CODE_MND_WRONG_TOTP_CODE TAOS_DEF_ERROR_CODE(0, 0x035B) #define TSDB_CODE_MND_TOO_MANY_USER_IP_RANGE TAOS_DEF_ERROR_CODE(0, 0x035C) #define TSDB_CODE_MND_TOO_MANY_USER_TIME_RANGE TAOS_DEF_ERROR_CODE(0, 0x035D) diff --git a/include/util/totp.h b/include/util/totp.h new file mode 100644 index 00000000000..286639cafcd --- /dev/null +++ b/include/util/totp.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 TAOS Data, Inc. + * + * This program is free software: you can use, redistribute, and/or modify + * it under the terms of the GNU Affero General Public License, version 3 + * or later ("AGPL"), as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + + +#ifndef _TD_UTIL_TOTP_H_ +#define _TD_UTIL_TOTP_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +// format the TOTP code with leading zeros to match the specified digit length. +// return the number of characters written, excluding the null terminator. +int taosFormatTotp(int32_t totp, int digits, char *buffer, size_t size); + +// generate TOTP code for given secret at current time. +// secret is a byte array, not a base32 string. +// return the generated TOTP code, or -1 on error. +int32_t taosGenerateTotpCode(const uint8_t *secret, size_t secretLen, int digits); + +// verify TOTP code for given secret at current time with allowed window. +// secret is a byte array, not a base32 string. +// return 1 if the code is correct, 0 otherwise. +int taosVerifyTotpCode(const uint8_t *secret, size_t secretLen, int32_t userCode, int digits, int window); + +// generate TOTP secret from seed +// if seedLen is 0, the seed is treated as a null-terminated string and its length is determined using strlen. +// secret is a byte array, not a base32 string. +// return the length of the generated secret, or -1 on error. +int taosGenerateTotpSecret(const char *seed, size_t seedLen, uint8_t *secret, size_t secretLen); + + +#ifdef __cplusplus +} +#endif + +#endif /*_TD_UTIL_TOTP_H_*/ diff --git a/include/util/tutil.h b/include/util/tutil.h index f308c24cd80..ba5b39882fc 100644 --- a/include/util/tutil.h +++ b/include/util/tutil.h @@ -381,6 +381,9 @@ bool taosIsBigChar(char c); bool taosIsSmallChar(char c); bool taosIsNumberChar(char c); bool taosIsSpecialChar(char c); +// check if the string is a complex string, a complex string contains +// at least 3 types of characters: upper, lower, digit, special +bool taosIsComplexString(const char* str); #ifdef __cplusplus } diff --git a/source/client/inc/clientInt.h b/source/client/inc/clientInt.h index 66a7fbe6842..91acb077321 100644 --- a/source/client/inc/clientInt.h +++ b/source/client/inc/clientInt.h @@ -186,8 +186,8 @@ typedef struct STscObj { SAppInstInfo* pAppInfo; SHashObj* pRequests; SPassInfo passInfo; - SWhiteListInfo whiteListInfo; - SWhiteListInfo dateTimeWhiteListInfo; + SWhiteListInfo whiteListInfo; // ip white list info + SWhiteListInfo dateTimeWhiteListInfo; // date time white list info STscNotifyInfo userDroppedInfo; SOptionInfo optionInfo; } STscObj; @@ -394,7 +394,7 @@ typedef struct AsyncArg { bool persistConnForSpecificMsg(void* parenct, tmsg_t msgType); void processMsgFromServer(void* parent, SRpcMsg* pMsg, SEpSet* pEpSet); -int32_t taos_connect_internal(const char* ip, const char* user, const char* pass, const char* auth, const char* db, +int32_t taos_connect_internal(const char* ip, const char* user, const char* pass, const char* auth, const char* totp, const char* db, uint16_t port, int connType, STscObj** pObj); int32_t parseSql(SRequestObj* pRequest, bool topicQuery, SQuery** pQuery, SStmtCallback* pStmtCb); diff --git a/source/client/src/clientImpl.c b/source/client/src/clientImpl.c index 709a3da980e..048ce85112c 100644 --- a/source/client/src/clientImpl.c +++ b/source/client/src/clientImpl.c @@ -33,7 +33,7 @@ #include "tversion.h" static int32_t initEpSetFromCfg(const char* firstEp, const char* secondEp, SCorEpSet* pEpSet); -static int32_t buildConnectMsg(SRequestObj* pRequest, SMsgSendInfo** pMsgSendInfo); +static int32_t buildConnectMsg(SRequestObj* pRequest, SMsgSendInfo** pMsgSendInfo, int32_t totpCode); void setQueryRequest(int64_t rId) { SRequestObj* pReq = acquireRequest(rId); @@ -143,10 +143,10 @@ void cleanupAppInfo() { tscInfo("cluster instance map cleaned"); } -static int32_t taosConnectImpl(const char* user, const char* auth, const char* db, __taos_async_fn_t fp, void* param, +static int32_t taosConnectImpl(const char* user, const char* auth, int32_t totpCode, const char* db, __taos_async_fn_t fp, void* param, SAppInstInfo* pAppInfo, int connType, STscObj** pTscObj); -int32_t taos_connect_internal(const char* ip, const char* user, const char* pass, const char* auth, const char* db, +int32_t taos_connect_internal(const char* ip, const char* user, const char* pass, const char* auth, const char* totp, const char* db, uint16_t port, int connType, STscObj** pObj) { TSC_ERR_RET(taos_init()); if (!validateUserName(user)) { @@ -175,6 +175,15 @@ int32_t taos_connect_internal(const char* ip, const char* user, const char* pass tstrncpy(secretEncrypt, auth, tListLen(secretEncrypt)); } + int32_t totpCode = -1; + if (totp != NULL) { + char *endptr = NULL; + totpCode = taosStr2Int32(totp, &endptr, 10); + if (endptr == totp || *endptr != '\0' || totpCode < 0 || totpCode > 999999) { + TSC_ERR_RET(TSDB_CODE_TSC_INVALID_TOTP_CODE); + } + } + SCorEpSet epSet = {0}; if (ip) { TSC_ERR_RET(initEpSetFromCfg(ip, NULL, &epSet)); @@ -267,7 +276,7 @@ _return: tscError("failed to unlock app info, code:%s", tstrerror(TAOS_SYSTEM_ERROR(code))); return code; } - return taosConnectImpl(user, &secretEncrypt[0], localDb, NULL, NULL, *pInst, connType, pObj); + return taosConnectImpl(user, &secretEncrypt[0], totpCode, localDb, NULL, NULL, *pInst, connType, pObj); } } @@ -1688,7 +1697,7 @@ int32_t initEpSetFromCfg(const char* firstEp, const char* secondEp, SCorEpSet* p return 0; } -int32_t taosConnectImpl(const char* user, const char* auth, const char* db, __taos_async_fn_t fp, void* param, +int32_t taosConnectImpl(const char* user, const char* auth, int32_t totpCode, const char* db, __taos_async_fn_t fp, void* param, SAppInstInfo* pAppInfo, int connType, STscObj** pTscObj) { *pTscObj = NULL; int32_t code = createTscObj(user, auth, db, connType, pAppInfo, pTscObj); @@ -1711,7 +1720,7 @@ int32_t taosConnectImpl(const char* user, const char* auth, const char* db, __ta } SMsgSendInfo* body = NULL; - code = buildConnectMsg(pRequest, &body); + code = buildConnectMsg(pRequest, &body, totpCode); if (TSDB_CODE_SUCCESS != code) { destroyTscObj(*pTscObj); return code; @@ -1739,15 +1748,22 @@ int32_t taosConnectImpl(const char* user, const char* auth, const char* db, __ta taos_close_internal(*pTscObj); *pTscObj = NULL; return terrno; - } else { - tscInfo("conn:0x%" PRIx64 ", connection is opening, connId:%u, dnodeConn:%p, QID:0x%" PRIx64, (*pTscObj)->id, - (*pTscObj)->connId, (*pTscObj)->pAppInfo->pTransporter, pRequest->requestId); - destroyRequest(pRequest); } + if (connType == CONN_TYPE__AUTH_TEST) { + terrno = TSDB_CODE_SUCCESS; + destroyRequest(pRequest); + taos_close_internal(*pTscObj); + *pTscObj = NULL; + return TSDB_CODE_SUCCESS; + } + + tscInfo("conn:0x%" PRIx64 ", connection is opening, connId:%u, dnodeConn:%p, QID:0x%" PRIx64, (*pTscObj)->id, + (*pTscObj)->connId, (*pTscObj)->pAppInfo->pTransporter, pRequest->requestId); + destroyRequest(pRequest); return code; } -static int32_t buildConnectMsg(SRequestObj* pRequest, SMsgSendInfo** pMsgSendInfo) { +static int32_t buildConnectMsg(SRequestObj* pRequest, SMsgSendInfo** pMsgSendInfo, int32_t totpCode) { *pMsgSendInfo = taosMemoryCalloc(1, sizeof(SMsgSendInfo)); if (*pMsgSendInfo == NULL) { return terrno; @@ -1781,6 +1797,7 @@ static int32_t buildConnectMsg(SRequestObj* pRequest, SMsgSendInfo** pMsgSendInf connectReq.connType = pObj->connType; connectReq.pid = appInfo.pid; connectReq.startTime = appInfo.startTime; + connectReq.totpCode = totpCode; tstrncpy(connectReq.app, appInfo.appName, sizeof(connectReq.app)); tstrncpy(connectReq.user, pObj->user, sizeof(connectReq.user)); @@ -1990,6 +2007,56 @@ _exit: } } + + +TAOS *taos_connect_totp(const char *ip, const char *user, const char *pass, const char* totp, const char *db, uint16_t port) { + tscInfo("try to connect to %s:%u by totp, user:%s db:%s", ip, port, user, db); + if (user == NULL) { + user = TSDB_DEFAULT_USER; + } + + if (pass == NULL) { + pass = TSDB_DEFAULT_PASS; + } + + STscObj *pObj = NULL; + int32_t code = taos_connect_internal(ip, user, pass, NULL, totp, db, port, CONN_TYPE__QUERY, &pObj); + if (TSDB_CODE_SUCCESS == code) { + int64_t *rid = taosMemoryCalloc(1, sizeof(int64_t)); + if (NULL == rid) { + tscError("out of memory when taos connect to %s:%u, user:%s db:%s", ip, port, user, db); + return NULL; + } + *rid = pObj->id; + return (TAOS *)rid; + } else { + terrno = code; + } + + return NULL; +} + + +int taos_connect_test(const char *ip, const char *user, const char *pass, const char* totp, const char *db, uint16_t port) { + tscInfo("try to connect to %s:%u by totp, user:%s db:%s", ip, port, user, db); + if (user == NULL) { + user = TSDB_DEFAULT_USER; + } + + if (pass == NULL) { + pass = TSDB_DEFAULT_PASS; + } + + STscObj *pObj = NULL; + return taos_connect_internal(ip, user, pass, NULL, totp, db, port, CONN_TYPE__AUTH_TEST, &pObj); +} + + +TAOS *taos_connect_token(const char *ip, const char *token, const char *db, uint16_t port) { + return NULL; +} + + TAOS* taos_connect_auth(const char* ip, const char* user, const char* auth, const char* db, uint16_t port) { tscInfo("try to connect to %s:%u by auth, user:%s db:%s", ip, port, user, db); if (user == NULL) { @@ -2002,7 +2069,7 @@ TAOS* taos_connect_auth(const char* ip, const char* user, const char* auth, cons } STscObj* pObj = NULL; - int32_t code = taos_connect_internal(ip, user, NULL, auth, db, port, CONN_TYPE__QUERY, &pObj); + int32_t code = taos_connect_internal(ip, user, NULL, auth, NULL, db, port, CONN_TYPE__QUERY, &pObj); if (TSDB_CODE_SUCCESS == code) { int64_t* rid = taosMemoryCalloc(1, sizeof(int64_t)); if (NULL == rid) { diff --git a/source/client/src/clientMain.c b/source/client/src/clientMain.c index 972c7dfcc98..e90ff9c69fa 100644 --- a/source/client/src/clientMain.c +++ b/source/client/src/clientMain.c @@ -319,7 +319,7 @@ TAOS *taos_connect(const char *ip, const char *user, const char *pass, const cha } STscObj *pObj = NULL; - int32_t code = taos_connect_internal(ip, user, pass, NULL, db, port, CONN_TYPE__QUERY, &pObj); + int32_t code = taos_connect_internal(ip, user, pass, NULL, NULL, db, port, CONN_TYPE__QUERY, &pObj); if (TSDB_CODE_SUCCESS == code) { int64_t *rid = taosMemoryCalloc(1, sizeof(int64_t)); if (NULL == rid) { diff --git a/source/client/src/clientMsgHandler.c b/source/client/src/clientMsgHandler.c index 0d38438bb4c..f5e203bddcb 100644 --- a/source/client/src/clientMsgHandler.c +++ b/source/client/src/clientMsgHandler.c @@ -74,6 +74,11 @@ int32_t processConnectRsp(void* param, SDataBuf* pMsg, int32_t code) { goto End; } + if (pTscObj->connType == CONN_TYPE__AUTH_TEST) { + // auth test connection, no need to process connect rsp + goto End; + } + SConnectRsp connectRsp = {0}; if (tDeserializeSConnectRsp(pMsg->pData, pMsg->len, &connectRsp) != 0) { code = TSDB_CODE_TSC_INVALID_VERSION; diff --git a/source/client/src/clientTmq.c b/source/client/src/clientTmq.c index 2bdc43818e1..095f0a71019 100644 --- a/source/client/src/clientTmq.c +++ b/source/client/src/clientTmq.c @@ -1841,7 +1841,7 @@ tmq_t* tmq_consumer_new(tmq_conf_t* conf, char* errstr, int32_t errstrLen) { } // init connection - code = taos_connect_internal(conf->ip, user, pass, NULL, NULL, conf->port, CONN_TYPE__TMQ, &pTmq->pTscObj); + code = taos_connect_internal(conf->ip, user, pass, NULL, NULL, NULL, conf->port, CONN_TYPE__TMQ, &pTmq->pTscObj); if (code) { terrno = code; tqErrorC("consumer:0x%" PRIx64 " setup failed since %s, groupId:%s", pTmq->consumerId, terrstr(), pTmq->groupId); diff --git a/source/client/test/clientTests.cpp b/source/client/test/clientTests.cpp index 1fc0bd78d16..397f3e23abc 100644 --- a/source/client/test/clientTests.cpp +++ b/source/client/test/clientTests.cpp @@ -19,6 +19,7 @@ #include "osSemaphore.h" #include "taoserror.h" #include "thash.h" +#include "totp.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wwrite-strings" @@ -338,6 +339,62 @@ TEST(clientCase, connect_Test) { taos_close(pConn); } + +TEST(clientCase, connect_totp_Test) { + taos_options(TSDB_OPTION_CONFIGDIR, "~/first/cfg"); + TAOS* pConn = taos_connect("localhost", "root", "taosdata", NULL, 0); + if (pConn == NULL) { + (void)printf("failed to connect to server, reason:%s\n", taos_errstr(NULL)); + } + + uint8_t secret[64] = {0}; + size_t secretLen = taosGenerateTotpSecret("AAbb1122", 8, secret, sizeof(secret)); + + TAOS_RES* pRes = taos_query(pConn, "create user totp_u pass 'taosdata' totpseed 'AAbb1122'"); + if (taos_errno(pRes) != 0) { + (void)printf("error in create user, reason:%s\n", taos_errstr(pRes)); + } + taos_free_result(pRes); + taos_close(pConn); + + + int totpCode = taosGenerateTotpCode(secret, secretLen, 6); + pConn = taos_connect_totp("localhost", "totp_u", "taosdata", "123456", NULL, 0); + if (pConn != NULL) { + (void)printf("connect to server with wrong totp"); + taos_close(pConn); + } + + int code = taos_connect_test("localhost", "totp_u", "taosdata", "123456", NULL, 0); + if (code != TSDB_CODE_MND_WRONG_TOTP_CODE) { + (void)printf("test connect to server with wrong totp return wrong code:%d\n", code); + taos_close(pConn); + } + + char totp[16] = {0}; + (void)taosFormatTotp(totpCode, 6, totp, sizeof(totp)); + pConn = taos_connect_totp("localhost", "totp_u", "taosdata", totp, NULL, 0); + if (pConn == NULL) { + (void)printf("failed to connect to server with totp, reason:%s\n", taos_errstr(NULL)); + } + + pRes = taos_query(pConn, "show users"); + if (taos_errno(pRes) != 0) { + (void)printf("error in create user, reason:%s\n", taos_errstr(pRes)); + } + taos_free_result(pRes); + + taos_close(pConn); + + totpCode = taosGenerateTotpCode(secret, secretLen, 6); + (void)taosFormatTotp(totpCode, 6, totp, sizeof(totp)); + code = taos_connect_test("localhost", "totp_u", "taosdata", totp, NULL, 0); + if (code != 0) { + (void)printf("test connect to server with correct totp return wrong code:%d\n", code); + } +} + + TEST(clientCase, connect_with_dsn_Test) { TAOS* pConn = taos_connect_with_dsn(NULL); ASSERT_EQ(pConn, nullptr); diff --git a/source/client/wrapper/inc/wrapper.h b/source/client/wrapper/inc/wrapper.h index de051524375..f5fb1653eb6 100644 --- a/source/client/wrapper/inc/wrapper.h +++ b/source/client/wrapper/inc/wrapper.h @@ -42,6 +42,9 @@ extern void (*fp_taos_cleanup)(void); extern int (*fp_taos_options)(TSDB_OPTION option, const void *arg, ...); extern int (*fp_taos_options_connection)(TAOS *taos, TSDB_OPTION_CONNECTION option, const void *arg, ...); extern TAOS *(*fp_taos_connect)(const char *ip, const char *user, const char *pass, const char *db, uint16_t port); +extern TAOS *(*fp_taos_connect_totp)(const char *ip, const char *user, const char *pass, const char* totp, const char *db, uint16_t port); +extern int (*fp_taos_connect_test)(const char *ip, const char *user, const char *pass, const char* totp, const char *db, uint16_t port); +extern TAOS *(*fp_taos_connect_token)(const char *ip, const char *token, const char *db, uint16_t port); extern TAOS *(*fp_taos_connect_auth)(const char *ip, const char *user, const char *auth, const char *db, uint16_t port); extern TAOS *(*fp_taos_connect_with_dsn)(const char *dsn); extern void (*fp_taos_close)(TAOS *taos); diff --git a/source/client/wrapper/src/wrapperDriver.c b/source/client/wrapper/src/wrapperDriver.c index e9976868491..051991d48e6 100644 --- a/source/client/wrapper/src/wrapperDriver.c +++ b/source/client/wrapper/src/wrapperDriver.c @@ -95,6 +95,9 @@ int32_t taosDriverInit(EDriverType driverType) { LOAD_FUNC(fp_taos_options, "taos_options"); LOAD_FUNC(fp_taos_options_connection, "taos_options_connection"); LOAD_FUNC(fp_taos_connect, "taos_connect"); + LOAD_FUNC(fp_taos_connect_totp, "taos_connect_totp"); + LOAD_FUNC(fp_taos_connect_token, "taos_connect_token"); + LOAD_FUNC(fp_taos_connect_test, "taos_connect_test"); LOAD_FUNC(fp_taos_connect_auth, "taos_connect_auth"); LOAD_FUNC(fp_taos_connect_with_dsn, "taos_connect_with_dsn"); LOAD_FUNC(fp_taos_close, "taos_close"); diff --git a/source/client/wrapper/src/wrapperFunc.c b/source/client/wrapper/src/wrapperFunc.c index a4dc9c46557..8df41b142d2 100644 --- a/source/client/wrapper/src/wrapperFunc.c +++ b/source/client/wrapper/src/wrapperFunc.c @@ -154,6 +154,39 @@ TAOS *taos_connect(const char *ip, const char *user, const char *pass, const cha return (*fp_taos_connect)(ip, user, pass, db, port); } +TAOS *taos_connect_totp(const char *ip, const char *user, const char *pass, const char* totp, const char *db, uint16_t port) { + if (taos_init() != 0) { + terrno = TSDB_CODE_DLL_NOT_LOAD; + return NULL; + } + + CHECK_PTR(fp_taos_connect_totp); + return (*fp_taos_connect_totp)(ip, user, pass, totp, db, port); +} + +int taos_connect_test(const char *ip, const char *user, const char *pass, const char* totp, const char *db, uint16_t port) { + if (taos_init() != 0) { + return TSDB_CODE_DLL_NOT_LOAD; + } + if (tsDriver == NULL) { + return TSDB_CODE_DLL_NOT_LOAD; + } + if (fp_taos_connect_test == NULL) { + return TSDB_CODE_DLL_FUNC_NOT_LOAD; + } + return (*fp_taos_connect_test)(ip, user, pass, totp, db, port); +} + +TAOS *taos_connect_token(const char *ip, const char *token, const char *db, uint16_t port) { + if (taos_init() != 0) { + terrno = TSDB_CODE_DLL_NOT_LOAD; + return NULL; + } + + CHECK_PTR(fp_taos_connect_token); + return (*fp_taos_connect_token)(ip, token, db, port); +} + TAOS *taos_connect_auth(const char *ip, const char *user, const char *auth, const char *db, uint16_t port) { if (taos_init() != 0) { terrno = TSDB_CODE_DLL_NOT_LOAD; diff --git a/source/client/wrapper/src/wrapperVariable.c b/source/client/wrapper/src/wrapperVariable.c index 55b892cb59d..1b17432ec0f 100644 --- a/source/client/wrapper/src/wrapperVariable.c +++ b/source/client/wrapper/src/wrapperVariable.c @@ -22,6 +22,9 @@ void (*fp_taos_cleanup)(void) = NULL; int (*fp_taos_options)(TSDB_OPTION option, const void *arg, ...) = NULL; int (*fp_taos_options_connection)(TAOS *taos, TSDB_OPTION_CONNECTION option, const void *arg, ...) = NULL; TAOS *(*fp_taos_connect)(const char *ip, const char *user, const char *pass, const char *db, uint16_t port) = NULL; +TAOS *(*fp_taos_connect_totp)(const char *ip, const char *user, const char *pass, const char* totp, const char *db, uint16_t port) = NULL; +int (*fp_taos_connect_test)(const char *ip, const char *user, const char *pass, const char* totp, const char *db, uint16_t port) = NULL; +TAOS *(*fp_taos_connect_token)(const char *ip, const char *token, const char *db, uint16_t port) = NULL; TAOS *(*fp_taos_connect_auth)(const char *ip, const char *user, const char *auth, const char *db, uint16_t port) = NULL; TAOS *(*fp_taos_connect_with_dsn)(const char *dsn) = NULL; void (*fp_taos_close)(TAOS *taos) = NULL; diff --git a/source/common/src/msg/tmsg.c b/source/common/src/msg/tmsg.c index d7b5894653c..51a70a01919 100644 --- a/source/common/src/msg/tmsg.c +++ b/source/common/src/msg/tmsg.c @@ -9358,6 +9358,7 @@ int32_t tSerializeSConnectReq(void *buf, int32_t bufLen, SConnectReq *pReq) { TAOS_CHECK_EXIT(tEncodeCStrWithLen(&encoder, pReq->passwd, TSDB_PASSWORD_LEN)); TAOS_CHECK_EXIT(tEncodeI64(&encoder, pReq->startTime)); TAOS_CHECK_EXIT(tEncodeCStr(&encoder, pReq->sVer)); + TAOS_CHECK_EXIT(tEncodeI32(&encoder, pReq->totpCode)); tEndEncode(&encoder); _exit: @@ -9390,6 +9391,12 @@ int32_t tDeserializeSConnectReq(void *buf, int32_t bufLen, SConnectReq *pReq) { TAOS_CHECK_EXIT(TSDB_CODE_VERSION_NOT_COMPATIBLE); } TAOS_CHECK_EXIT(tDecodeCStrTo(&decoder, pReq->sVer)); + // Check the client version from version 3.4.0.0 + if (tDecodeIsEnd(&decoder)) { + tDecoderClear(&decoder); + TAOS_CHECK_EXIT(TSDB_CODE_VERSION_NOT_COMPATIBLE); + } + TAOS_CHECK_EXIT(tDecodeI32(&decoder, &pReq->totpCode)); tEndDecode(&decoder); _exit: @@ -9422,7 +9429,6 @@ int32_t tSerializeSConnectRsp(void *buf, int32_t bufLen, SConnectRsp *pRsp) { TAOS_CHECK_EXIT(tSerializeSMonitorParas(&encoder, &pRsp->monitorParas)); TAOS_CHECK_EXIT(tEncodeI8(&encoder, pRsp->enableAuditDelete)); TAOS_CHECK_EXIT(tEncodeI64(&encoder, pRsp->timeWhiteListVer)); - TAOS_CHECK_EXIT(tEncodeI8(&encoder, pRsp->mustChangePass)); tEndEncode(&encoder); _exit: @@ -9481,10 +9487,8 @@ int32_t tDeserializeSConnectRsp(void *buf, int32_t bufLen, SConnectRsp *pRsp) { } if (!tDecodeIsEnd(&decoder)) { TAOS_CHECK_EXIT(tDecodeI64(&decoder, &pRsp->timeWhiteListVer)); - TAOS_CHECK_EXIT(tDecodeI8(&decoder, &pRsp->mustChangePass)); } else { pRsp->timeWhiteListVer = 0; - pRsp->mustChangePass = 0; } tEndDecode(&decoder); diff --git a/source/common/src/systable.c b/source/common/src/systable.c index be206e203ca..07fb557d7e2 100644 --- a/source/common/src/systable.c +++ b/source/common/src/systable.c @@ -291,6 +291,7 @@ static const SSysDbTableSchema userUsersSchema[] = { {.name = "sysinfo", .bytes = 1, .type = TSDB_DATA_TYPE_TINYINT, .sysInfo = true}, {.name = "createdb", .bytes = 1, .type = TSDB_DATA_TYPE_TINYINT, .sysInfo = true}, {.name = "create_time", .bytes = 8, .type = TSDB_DATA_TYPE_TIMESTAMP, .sysInfo = true}, + {.name = "totp", .bytes = 1, .type = TSDB_DATA_TYPE_TINYINT, .sysInfo = true}, {.name = "allowed_host", .bytes = TSDB_PRIVILEDGE_HOST_LEN + VARSTR_HEADER_SIZE, .type = TSDB_DATA_TYPE_VARCHAR, .sysInfo = true}, {.name = "allowed_datetime", .bytes = TSDB_PRIVILEDGE_HOST_LEN + VARSTR_HEADER_SIZE, .type = TSDB_DATA_TYPE_VARCHAR, .sysInfo = true}, }; @@ -302,6 +303,7 @@ static const SSysDbTableSchema userUsersFullSchema[] = { {.name = "sysinfo", .bytes = 1, .type = TSDB_DATA_TYPE_TINYINT, .sysInfo = true}, {.name = "createdb", .bytes = 1, .type = TSDB_DATA_TYPE_TINYINT, .sysInfo = true}, {.name = "create_time", .bytes = 8, .type = TSDB_DATA_TYPE_TIMESTAMP, .sysInfo = true}, + {.name = "totp", .bytes = 1, .type = TSDB_DATA_TYPE_TINYINT, .sysInfo = true}, {.name = "change_pass", .bytes = 1, .type = TSDB_DATA_TYPE_TINYINT, .sysInfo = true}, {.name = "encrypted_pass", .bytes = TSDB_PASSWORD_LEN + VARSTR_HEADER_SIZE, .type = TSDB_DATA_TYPE_VARCHAR, .sysInfo = true}, {.name = "session_per_user", .bytes = 4, .type = TSDB_DATA_TYPE_INT, .sysInfo = true}, diff --git a/source/dnode/mnode/impl/inc/mndDef.h b/source/dnode/mnode/impl/inc/mndDef.h index 9411fd42560..9df453f80da 100644 --- a/source/dnode/mnode/impl/inc/mndDef.h +++ b/source/dnode/mnode/impl/inc/mndDef.h @@ -404,8 +404,8 @@ typedef struct { typedef struct { - int64_t lastLoginTime; // in seconds - int64_t lastFailedLoginTime; // in seconds + int32_t lastLoginTime; // in seconds + int32_t lastFailedLoginTime; // in seconds int32_t failedLoginCount; } SLoginInfo; @@ -422,7 +422,7 @@ typedef struct { typedef struct { char pass[TSDB_PASSWORD_LEN]; - int64_t setTime; // password set time, in seconds + int32_t setTime; // password set time, in seconds } SUserPassword; typedef struct { diff --git a/source/dnode/mnode/impl/inc/mndUser.h b/source/dnode/mnode/impl/inc/mndUser.h index d820785d181..0ae16ad78e8 100644 --- a/source/dnode/mnode/impl/inc/mndUser.h +++ b/source/dnode/mnode/impl/inc/mndUser.h @@ -56,6 +56,7 @@ int64_t mndGetUserTimeWhiteListVer(SMnode *pMnode, SUserObj *pUser); void mndGetUserLoginInfo(const char *user, SLoginInfo *pLoginInfo); void mndSetUserLoginInfo(const char *user, const SLoginInfo *pLoginInfo); +bool mndIsTotpEnabledUser(SUserObj *pUser); #ifdef __cplusplus } diff --git a/source/dnode/mnode/impl/src/mndProfile.c b/source/dnode/mnode/impl/src/mndProfile.c index d1eb901f3fa..ee38b227587 100644 --- a/source/dnode/mnode/impl/src/mndProfile.c +++ b/source/dnode/mnode/impl/src/mndProfile.c @@ -29,6 +29,7 @@ #include "mndView.h" #include "tglobal.h" #include "tversion.h" +#include "totp.h" typedef struct { uint32_t id; @@ -293,12 +294,40 @@ static int32_t mndCountUserConns(SMnode *pMnode, const char *user) { -static bool constTimeEq(const char *a, const char *b, size_t len) { - volatile uint8_t res = 0; - for (size_t i = 0; i < len; i++) { - res |= a[i] ^ b[i]; +static int32_t verifyPassword(SUserObj* pUser, const char* inputPass) { + int32_t code = 0; + + const char* currPass = pUser->passwords[0].pass; + char pass[TSDB_PASSWORD_LEN] = {0}; + (void)memcpy(pass, inputPass, TSDB_PASSWORD_LEN); + pass[TSDB_PASSWORD_LEN - 1] = 0; + + if (pUser->passEncryptAlgorithm != 0) { + if (pUser->passEncryptAlgorithm != tsiEncryptPassAlgorithm) { + return TSDB_CODE_DNODE_INVALID_ENCRYPTKEY; + } + code = mndEncryptPass(pass, pUser->salt, NULL); + if (code != TSDB_CODE_SUCCESS) { + return code; + } } - return res == 0; + + // constant time comparison to prevent timing attack + volatile uint8_t res = 0; + for (size_t i = 0; i < sizeof(pass) - 1; i++) { + res |= pass[i] ^ currPass[i]; + } + + return (res == 0) ? TSDB_CODE_SUCCESS: TSDB_CODE_MND_AUTH_FAILURE; +} + + + +static bool verifyTotp(SUserObj *pUser, int32_t totpCode) { + if (!mndIsTotpEnabledUser(pUser)) { + return true; + } + return taosVerifyTotpCode(pUser->totpsecret, sizeof(pUser->totpsecret), totpCode, 6, 1) != 0; } @@ -336,11 +365,11 @@ static int32_t mndProcessConnectReq(SRpcMsg *pReq) { goto _OVER; } - int64_t now = taosGetTimestampSec(); + int32_t now = taosGetTimestampSec(); if (pUser->passwordLifeTime > 0 && pUser->passwordGraceTime >= 0) { - int64_t lifeTime = now - pUser->passwords[0].setTime; - int64_t maxLifeTime = pUser->passwordLifeTime + pUser->passwordGraceTime; - if (lifeTime >= maxLifeTime) { + int32_t age = now - pUser->passwords[0].setTime; + int32_t maxLifeTime = pUser->passwordLifeTime + pUser->passwordGraceTime; + if (age >= maxLifeTime) { mGError("user:%s, failed to login from %s since password expired", pReq->info.conn.user, ip); code = TSDB_CODE_MND_USER_PASSWORD_EXPIRED; goto _OVER; @@ -378,24 +407,22 @@ static int32_t mndProcessConnectReq(SRpcMsg *pReq) { } } - char tmpPass[TSDB_PASSWORD_LEN] = {0}; - (void)memcpy(tmpPass, connReq.passwd, TSDB_PASSWORD_LEN); - tmpPass[TSDB_PASSWORD_LEN - 1] = 0; - - if (pUser->passEncryptAlgorithm != 0) { - if (pUser->passEncryptAlgorithm != tsiEncryptPassAlgorithm) { - code = TSDB_CODE_DNODE_INVALID_ENCRYPTKEY; - goto _OVER; - } - TAOS_CHECK_GOTO(mndEncryptPass(tmpPass, pUser->salt, NULL), NULL, _OVER); - } - if (tsMndSkipGrant) { loginInfo.lastLoginTime= now; - } else if (constTimeEq(tmpPass, pUser->passwords[0].pass, sizeof(tmpPass) - 1)) { + if (connReq.connType != CONN_TYPE__AUTH_TEST) { + mndSetUserLoginInfo(pReq->info.conn.user, &loginInfo); + } + } else if (!verifyTotp(pUser, connReq.totpCode)) { + mGError("user:%s, failed to login from %s since wrong TOTP code, input:%06d", pReq->info.conn.user, ip, connReq.totpCode); + code = TSDB_CODE_MND_WRONG_TOTP_CODE; + goto _OVER; + } else if ((code = verifyPassword(pUser, connReq.passwd)) == TSDB_CODE_SUCCESS) { loginInfo.failedLoginCount = 0; loginInfo.lastLoginTime= now; - } else { + if (connReq.connType != CONN_TYPE__AUTH_TEST) { + mndSetUserLoginInfo(pReq->info.conn.user, &loginInfo); + } + } else if (code == TSDB_CODE_MND_AUTH_FAILURE) { mGError("user:%s, failed to login from %s since pass not match, input:%s", pReq->info.conn.user, ip, connReq.passwd); if (pUser->failedLoginAttempts >= 0) { if (loginInfo.failedLoginCount >= pUser->failedLoginAttempts) { @@ -405,11 +432,12 @@ static int32_t mndProcessConnectReq(SRpcMsg *pReq) { loginInfo.failedLoginCount++; loginInfo.lastFailedLoginTime = now; } - code = TSDB_CODE_MND_AUTH_FAILURE; - } - - mndSetUserLoginInfo(pReq->info.conn.user, &loginInfo); - if (code != 0) { + if (connReq.connType != CONN_TYPE__AUTH_TEST) { + mndSetUserLoginInfo(pReq->info.conn.user, &loginInfo); + } + goto _OVER; + } else { + mGError("user:%s, failed to login from %s since %s", pReq->info.conn.user, ip, tstrerror(code)); goto _OVER; } @@ -430,6 +458,11 @@ static int32_t mndProcessConnectReq(SRpcMsg *pReq) { TAOS_CHECK_GOTO(mndCheckDbPrivilege(pMnode, pReq->info.conn.user, MND_OPER_READ_OR_WRITE_DB, pDb), NULL, _OVER); } + if (connReq.connType == CONN_TYPE__AUTH_TEST) { + code = 0; + goto _OVER; + } + pConn = mndCreateConn(pMnode, pReq->info.conn.user, connReq.connType, &pReq->info.conn.cliAddr, connReq.pid, connReq.app, connReq.startTime, connReq.sVer); if (pConn == NULL) { @@ -446,7 +479,6 @@ static int32_t mndProcessConnectReq(SRpcMsg *pReq) { connectRsp.clusterId = pMnode->clusterId; connectRsp.connId = pConn->id; connectRsp.connType = connReq.connType; - connectRsp.mustChangePass = mndMustChangePassword(pUser) ? 1 : 0; connectRsp.dnodeNum = mndGetDnodeSize(pMnode); connectRsp.svrTimestamp = taosGetTimestampSec(); connectRsp.passVer = pUser->passVersion; diff --git a/source/dnode/mnode/impl/src/mndUser.c b/source/dnode/mnode/impl/src/mndUser.c index b3036e44431..a6be0c6736b 100644 --- a/source/dnode/mnode/impl/src/mndUser.c +++ b/source/dnode/mnode/impl/src/mndUser.c @@ -28,6 +28,7 @@ #include "mndTopic.h" #include "mndTrans.h" #include "tbase64.h" +#include "totp.h" // clang-format on @@ -956,7 +957,7 @@ static void dropOldPasswords(SUserObj *pUser) { reuseMax = 1; // keep at least one password } - int64_t now = taosGetTimestampSec(); + int32_t now = taosGetTimestampSec(); int32_t index = reuseMax; while(index < pUser->numOfPasswords) { SUserPassword *pPass = &pUser->passwords[index]; @@ -1208,7 +1209,7 @@ SSdbRaw *mndUserActionEncode(SUserObj *pUser) { SDB_SET_INT32(pRaw, dataPos, pUser->numOfPasswords, _OVER) for (int32_t i = 0; i < pUser->numOfPasswords; i++) { SDB_SET_BINARY(pRaw, dataPos, pUser->passwords[i].pass, sizeof(pUser->passwords[i].pass), _OVER) - SDB_SET_INT64(pRaw, dataPos, pUser->passwords[i].setTime, _OVER) + SDB_SET_INT32(pRaw, dataPos, pUser->passwords[i].setTime, _OVER) } SDB_SET_BINARY(pRaw, dataPos, pUser->salt, sizeof(pUser->salt), _OVER) @@ -1449,7 +1450,7 @@ static SSdbRow *mndUserActionDecode(SSdbRaw *pRaw) { } for (int32_t i = 0; i < pUser->numOfPasswords; ++i) { SDB_GET_BINARY(pRaw, dataPos, pUser->passwords[i].pass, sizeof(pUser->passwords[i].pass), _OVER); - SDB_GET_INT64(pRaw, dataPos, &pUser->passwords[i].setTime, _OVER); + SDB_GET_INT32(pRaw, dataPos, &pUser->passwords[i].setTime, _OVER); } SDB_GET_BINARY(pRaw, dataPos, pUser->salt, sizeof(pUser->salt), _OVER) } @@ -1458,7 +1459,7 @@ static SSdbRow *mndUserActionDecode(SSdbRaw *pRaw) { SDB_GET_INT64(pRaw, dataPos, &pUser->createdTime, _OVER) SDB_GET_INT64(pRaw, dataPos, &pUser->updateTime, _OVER) if (sver < USER_VER_SUPPORT_ADVANCED_SECURITY) { - pUser->passwords[0].setTime = pUser->updateTime / 1000; + pUser->passwords[0].setTime = (int32_t)(pUser->updateTime / 1000); } SDB_GET_INT8(pRaw, dataPos, &pUser->superUser, _OVER) @@ -2165,7 +2166,10 @@ static int32_t mndCreateUser(SMnode *pMnode, char *acct, SCreateUserReq *pCreate tstrncpy(userObj.user, pCreate->user, TSDB_USER_LEN); tstrncpy(userObj.acct, acct, TSDB_USER_LEN); if (pCreate->totpseed[0] != 0) { - // TODO: generate totp seed + int len = taosGenerateTotpSecret(pCreate->totpseed, 0, userObj.totpsecret, sizeof(userObj.totpsecret)); + if (len < 0) { + TAOS_CHECK_GOTO(TSDB_CODE_PAR_INVALID_OPTION_VALUE, &lino, _OVER); + } } userObj.createdTime = taosGetTimestampMs(); @@ -2333,7 +2337,6 @@ _OVER: } - static int32_t mndCheckPasswordFmt(const char *pwd) { if (strcmp(pwd, "taosdata") == 0) { return 0; @@ -2357,26 +2360,26 @@ static int32_t mndCheckPasswordFmt(const char *pwd) { return TSDB_CODE_PAR_NAME_OR_PASSWD_TOO_LONG; } - int32_t upper = 0, lower = 0, number = 0, special = 0; - for (int32_t i = 0; i < len; ++i) { - if (taosIsBigChar(pwd[i])) { - upper = 1; - } else if (taosIsSmallChar(pwd[i])) { - lower = 1; - } else if (taosIsNumberChar(pwd[i])) { - number = 1; - } else if (taosIsSpecialChar(pwd[i])) { - special = 1; - } else { - return TSDB_CODE_MND_INVALID_PASS_FORMAT; - } + if (taosIsComplexString(pwd)) { + return 0; } - if (upper + lower + number + special < 3) { - return TSDB_CODE_MND_INVALID_PASS_FORMAT; + return TSDB_CODE_MND_INVALID_PASS_FORMAT; +} + + + +static int32_t mndCheckTotpSeedFmt(const char *seed) { + int32_t len = strlen(seed); + if (len < TSDB_USER_TOTPSEED_MIN_LEN) { + return TSDB_CODE_PAR_OPTION_VALUE_TOO_SHORT; } - return 0; + if (taosIsComplexString(seed)) { + return 0; + } + + return TSDB_CODE_PAR_INVALID_OPTION_VALUE; } @@ -2565,6 +2568,11 @@ static int32_t mndProcessCreateUserReq(SRpcMsg *pReq) { TAOS_CHECK_GOTO(code, &lino, _OVER); } + if (createReq.totpseed[0] != 0) { + code = mndCheckTotpSeedFmt(createReq.totpseed); + TAOS_CHECK_GOTO(code, &lino, _OVER); + } + code = mndAcquireUser(pMnode, createReq.user, &pUser); if (pUser != NULL) { TAOS_CHECK_GOTO(TSDB_CODE_MND_USER_ALREADY_EXIST, &lino, _OVER); @@ -3201,7 +3209,11 @@ static int32_t mndProcessAlterUserReq(SRpcMsg *pReq) { } if (alterReq.hasTotpseed) { - // TODO: totp seed to secret + if (alterReq.totpseed[0] == 0) { // clear totp secret + memset(newUser.totpsecret, 0, sizeof(newUser.totpsecret)); + } else if (taosGenerateTotpSecret(alterReq.totpseed, 0, newUser.totpsecret, sizeof(newUser.totpsecret)) < 0) { + TAOS_CHECK_GOTO(TSDB_CODE_PAR_INVALID_OPTION_VALUE, &lino, _OVER); + } } if (alterReq.hasEnable) { @@ -3739,6 +3751,17 @@ _exit: TAOS_RETURN(code); } + +bool mndIsTotpEnabledUser(SUserObj *pUser) { + for (int32_t i = 0; i < sizeof(pUser->totpsecret); i++) { + if (pUser->totpsecret[i] != 0) { + return true; + } + } + return false; +} + + static int32_t mndRetrieveUsers(SRpcMsg *pReq, SShowObj *pShow, SSDataBlock *pBlock, int32_t rows) { SMnode *pMnode = pReq->info.node; SSdb *pSdb = pMnode->pSdb; @@ -3783,6 +3806,11 @@ static int32_t mndRetrieveUsers(SRpcMsg *pReq, SShowObj *pShow, SSDataBlock *pBl pColInfo = taosArrayGet(pBlock->pDataBlock, cols); COL_DATA_SET_VAL_GOTO((const char *)&pUser->createdTime, false, pUser, pShow->pIter, _exit); + cols++; + pColInfo = taosArrayGet(pBlock->pDataBlock, cols); + flag = mndIsTotpEnabledUser(pUser) ? 1 : 0; + COL_DATA_SET_VAL_GOTO((const char *)&flag, false, pUser, pShow->pIter, _exit); + cols++; int32_t tlen = convertIpWhiteListToStr(pUser, &buf); @@ -3886,6 +3914,11 @@ static int32_t mndRetrieveUsersFull(SRpcMsg *pReq, SShowObj *pShow, SSDataBlock pColInfo = taosArrayGet(pBlock->pDataBlock, cols); COL_DATA_SET_VAL_GOTO((const char *)&pUser->createdTime, false, pUser, pShow->pIter, _exit); + cols++; + pColInfo = taosArrayGet(pBlock->pDataBlock, cols); + flag = mndIsTotpEnabledUser(pUser) ? 1 : 0; + COL_DATA_SET_VAL_GOTO((const char *)&flag, false, pUser, pShow->pIter, _exit); + cols++; pColInfo = taosArrayGet(pBlock->pDataBlock, cols); COL_DATA_SET_VAL_GOTO((const char *)&pUser->changePass, false, pUser, pShow->pIter, _exit); diff --git a/source/libs/function/src/builtins.c b/source/libs/function/src/builtins.c index 198866fc016..64cd1d9bceb 100644 --- a/source/libs/function/src/builtins.c +++ b/source/libs/function/src/builtins.c @@ -6751,6 +6751,50 @@ const SBuiltinFuncDefinition funcMgtBuiltins[] = { .sprocessFunc = NULL, .finalizeFunc = NULL }, + { + .name = "generate_totp_secret", + .type = FUNCTION_TYPE_GENERATE_TOTP_SECRET, + .classification = FUNC_MGT_SCALAR_FUNC | FUNC_MGT_STRING_FUNC, + .parameters = {.minParamNum = 1, + .maxParamNum = 1, + .paramInfoPattern = 1, + .inputParaInfo[0][0] = {.isLastParam = true, + .startParam = 1, + .endParam = 1, + .validDataType = FUNC_PARAM_SUPPORT_VARCHAR_TYPE | FUNC_PARAM_SUPPORT_NCHAR_TYPE, + .validNodeType = FUNC_PARAM_SUPPORT_EXPR_NODE, + .paramAttribute = FUNC_PARAM_NO_SPECIFIC_ATTRIBUTE, + .valueRangeFlag = FUNC_PARAM_NO_SPECIFIC_VALUE,}, + .outputParaInfo = {.validDataType = FUNC_PARAM_SUPPORT_VARCHAR_TYPE | FUNC_PARAM_SUPPORT_NCHAR_TYPE}}, + .translateFunc = translateOutFirstIn, + .getEnvFunc = NULL, + .initFunc = NULL, + .sprocessFunc = generateTotpSecretFunction, + .finalizeFunc = NULL, + }, + { + // this function generates TOTP code based on the TOTP secret, + // it is not documented and only used for testing purpose to verify the correctness of TOTP code generation. + .name = "generate_totp_code", + .type = FUNCTION_TYPE_GENERATE_TOTP_CODE, + .classification = FUNC_MGT_SCALAR_FUNC | FUNC_MGT_STRING_FUNC, + .parameters = {.minParamNum = 1, + .maxParamNum = 1, + .paramInfoPattern = 1, + .inputParaInfo[0][0] = {.isLastParam = true, + .startParam = 1, + .endParam = 1, + .validDataType = FUNC_PARAM_SUPPORT_VARCHAR_TYPE | FUNC_PARAM_SUPPORT_NCHAR_TYPE, + .validNodeType = FUNC_PARAM_SUPPORT_EXPR_NODE, + .paramAttribute = FUNC_PARAM_NO_SPECIFIC_ATTRIBUTE, + .valueRangeFlag = FUNC_PARAM_NO_SPECIFIC_VALUE,}, + .outputParaInfo = {.validDataType = FUNC_PARAM_SUPPORT_VARCHAR_TYPE}}, + .translateFunc = translateOutFirstIn, + .getEnvFunc = NULL, + .initFunc = NULL, + .sprocessFunc = generateTotpCodeFunction, + .finalizeFunc = NULL, + }, }; // clang-format on diff --git a/source/libs/parser/inc/sql.y b/source/libs/parser/inc/sql.y index 18a3c582a2d..87ffb1c8fb1 100755 --- a/source/libs/parser/inc/sql.y +++ b/source/libs/parser/inc/sql.y @@ -112,6 +112,7 @@ user_enabled(A) ::= ENABLE NK_INTEGER(B). %type user_option { SUserOptions* } user_option(A) ::= TOTPSEED NK_STRING(B). { A = mergeUserOptions(pCxt, NULL, NULL); setUserOptionsTotpseed(pCxt, A, &B); } +user_option(A) ::= TOTPSEED NULL. { A = mergeUserOptions(pCxt, NULL, NULL); setUserOptionsTotpseed(pCxt, A, NULL); } user_option(A) ::= user_enabled(B). { A = mergeUserOptions(pCxt, NULL, NULL); A->enable = B; A->hasEnable = true; } user_option(A) ::= SYSINFO NK_INTEGER(B). { A = mergeUserOptions(pCxt, NULL, NULL); A->sysinfo = taosStr2Int8(B.z, NULL, 10); A->hasSysinfo = true; } user_option(A) ::= IS_IMPORT NK_INTEGER(B). { A = mergeUserOptions(pCxt, NULL, NULL); A->isImport = taosStr2Int8(B.z, NULL, 10); A->hasIsImport = true; } diff --git a/source/libs/parser/src/parAstCreater.c b/source/libs/parser/src/parAstCreater.c index 9422e250992..873557cb647 100644 --- a/source/libs/parser/src/parAstCreater.c +++ b/source/libs/parser/src/parAstCreater.c @@ -121,31 +121,11 @@ static bool isValidSimplePassword(const char* password) { -static bool isComplexString(const char* str) { - int hasUpper = 0, hasLower = 0, hasDigit = 0, hasSpecial = 0; - - for (char c = *str; c != 0; c = *(++str)) { - if (taosIsBigChar(c)) { - hasUpper = 1; - } else if (taosIsSmallChar(c)) { - hasLower = 1; - } else if (taosIsNumberChar(c)) { - hasDigit = 1; - } else if (taosIsSpecialChar(c)) { - hasSpecial = 1; - } - } - - return (hasUpper + hasLower + hasDigit + hasSpecial) >= 3; -} - - - static bool isValidStrongPassword(const char* password) { if (strcmp(password, "taosdata") == 0) { return true; } - return isComplexString(password); + return taosIsComplexString(password); } @@ -4275,6 +4255,11 @@ void setUserOptionsTotpseed(SAstCreateContext* pCxt, SUserOptions* pUserOptions, } pUserOptions->hasTotpseed = true; + if (pTotpseed == NULL) { // clear TOTP secret + memset(pUserOptions->totpseed, 0, sizeof(pUserOptions->totpseed)); + return; + } + if (pTotpseed->n >= sizeof(pUserOptions->totpseed) * 2) { pCxt->errCode = generateSyntaxErrMsg(&pCxt->msgBuf, TSDB_CODE_PAR_OPTION_VALUE_TOO_LONG, "TOTPSEED", sizeof(pUserOptions->totpseed)); return; @@ -4347,7 +4332,7 @@ static bool isValidUserOptions(SAstCreateContext* pCxt, const SUserOptions* opts return false; } - if (opts->hasTotpseed && !isComplexString(opts->totpseed)) { + if (opts->hasTotpseed && opts->totpseed[0] != 0 && !taosIsComplexString(opts->totpseed)) { pCxt->errCode = generateSyntaxErrMsg(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_OPTION_VALUE, "TOTPSEED"); return false; } diff --git a/source/libs/scalar/src/sclfunc.c b/source/libs/scalar/src/sclfunc.c index 00ee2386d16..2d27189df29 100644 --- a/source/libs/scalar/src/sclfunc.c +++ b/source/libs/scalar/src/sclfunc.c @@ -11,6 +11,7 @@ #include "tjson.h" #include "ttime.h" #include "tcompare.h" +#include "totp.h" typedef float (*_float_fn)(float); typedef float (*_float_fn_2)(float, float); @@ -1723,6 +1724,166 @@ _return: return code; } + + +static int32_t base32Encode(const uint8_t *in, int32_t inLen, char *out) { + int buffer = 0, bits = 0; + int outLen = 0; + + // process all input bytes + for (int i = 0; i < inLen; i++) { + buffer = (buffer << 8) | in[i]; + bits += 8; + + while (bits >= 5) { + int v = (buffer >> (bits - 5)) & 0x1F; + out[outLen++] = (v >= 26) ? (v - 26 + '2') : (v + 'A'); + bits -= 5; + } + } + + // process remaining bits + if (bits > 0) { + int v = (buffer << (5 - bits)) & 0x1F; + out[outLen++] = (v >= 26) ? (v - 26 + '2') : (v + 'A'); + } + + out[outLen] = '\0'; + return outLen; +} + + + +int32_t generateTotpSecretFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput) { + SColumnInfoData *pInputData = pInput->columnData; + SColumnInfoData *pOutputData = pOutput->columnData; + + char secret[64] = {0}; + int32_t bufLen = (sizeof(secret) * 8) / 5 + 4 + VARSTR_HEADER_SIZE + 1; + char *pOutputBuf = taosMemoryMalloc(bufLen); + if (!pOutputBuf) { + qError("generate_totp_secret function alloc memory failed"); + return terrno; + } + for (int32_t i = 0; i < pInput->numOfRows; ++i) { + if (colDataIsNull_s(pInputData, i)) { + colDataSetNULL(pOutputData, i); + continue; + } + char *input = colDataGetData(pInput[0].columnData, i); + + int len = taosGenerateTotpSecret(varDataVal(input), varDataLen(input), secret, sizeof(secret)); + if (len < 0) { + taosMemoryFree(pOutputBuf); + SCL_ERR_RET(TSDB_CODE_INTERNAL_ERROR); + } + + char *output = pOutputBuf; + len = base32Encode(secret, len, varDataVal(output)); + + varDataSetLen(output, len); + int32_t code = colDataSetVal(pOutputData, i, output, false); + + if (TSDB_CODE_SUCCESS != code) { + taosMemoryFree(pOutputBuf); + SCL_ERR_RET(code); + } + } + + pOutput->numOfRows = pInput->numOfRows; + taosMemoryFree(pOutputBuf); + + return TSDB_CODE_SUCCESS; +} + + +static int32_t base32Decode(const char *in, int32_t inLen, uint8_t *out) { + int buffer = 0, bits = 0; + int32_t outLen = 0; + + for (int32_t i = 0; i < inLen; i++) { + char c = in[i]; + + if (c >= 'a' && c <= 'z') { + c -= 'a'; + } else if (c >= 'A' && c <= 'Z') { + c -= 'A'; + } else if (c >= '2' && c <= '7') { + c = c - '2' + 26; + } else if (c == '=') { + break; // padding character + } else { + return -1; // invalid character + } + buffer = (buffer << 5) | c; + bits += 5; + if (bits >= 8) { + out[outLen++] = (buffer >> (bits - 8)) & 0xFF; + bits -= 8; + } + } + + return outLen; // success +} + + +// this function generates TOTP code based on the TOTP secret, +// it is not documented and only used for testing purpose to verify the correctness of +// TOTP code generation. +int32_t generateTotpCodeFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput) { + SColumnInfoData *pInputData = pInput->columnData; + SColumnInfoData *pOutputData = pOutput->columnData; + char *output = taosMemoryMalloc(VARSTR_HEADER_SIZE + 6 + 1); + if (!output) { + qError("generate_totp code function alloc memory failed"); + return terrno; + } + + uint8_t secret[64] = {0}; + int32_t secretLen = 0; + + for (int32_t i = 0; i < pInput->numOfRows; ++i) { + if (colDataIsNull_s(pInputData, i)) { + colDataSetNULL(pOutputData, i); + continue; + } + + char *in = colDataGetData(pInput[0].columnData, i); + if (varDataLen(in) > 100) { + colDataSetNULL(pOutputData, i); + qError("the %d secret is too long", i); + continue; + } + + secretLen = base32Decode(varDataVal(in), varDataLen(in), secret); + if (secretLen <= 0) { + qError("failed to decode the %d base32 secret", i); + colDataSetNULL(pOutputData, i); + continue; + } + + int64_t totp = taosGenerateTotpCode(secret, secretLen, 6); + if (totp < 0) { + qError("failed to generate the %d totp code", i); + colDataSetNULL(pOutputData, i); + continue; + } + + int len = taosFormatTotp(totp, 6, varDataVal(output), 7); + varDataSetLen(output, len); + int32_t code = colDataSetVal(pOutputData, i, output, false); + if (TSDB_CODE_SUCCESS != code) { + taosMemoryFree(output); + SCL_ERR_RET(code); + } + } + + pOutput->numOfRows = pInput->numOfRows; + taosMemoryFree(output); + return TSDB_CODE_SUCCESS; +} + + int32_t md5Function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput) { SColumnInfoData *pInputData = pInput->columnData; SColumnInfoData *pOutputData = pOutput->columnData; diff --git a/source/util/CMakeLists.txt b/source/util/CMakeLists.txt index c5467889592..f632ab4cac6 100644 --- a/source/util/CMakeLists.txt +++ b/source/util/CMakeLists.txt @@ -10,6 +10,9 @@ if(TD_LINUX) DEP_ext_xxhash(util) DEP_ext_lzma2(util) endif() +if (NOT ${TD_WINDOWS}) + DEP_ext_ssl(util) +endif() DEP_ext_geos(util) if(${BUILD_PCRE2}) # { DEP_ext_pcre2(util) diff --git a/source/util/src/terror.c b/source/util/src/terror.c index 03b976a10ce..21733a3ce45 100644 --- a/source/util/src/terror.c +++ b/source/util/src/terror.c @@ -178,6 +178,7 @@ TAOS_DEFINE_ERROR(TSDB_CODE_PAR_INVALID_COLUMN_REF, "Invalid column refere TAOS_DEFINE_ERROR(TSDB_CODE_PAR_INVALID_SLIDING_OFFSET, "Invalid sliding offset") TAOS_DEFINE_ERROR(TSDB_CODE_PAR_INVALID_INTERVAL_OFFSET, "Invalid interval offset") TAOS_DEFINE_ERROR(TSDB_CODE_NOT_SUPPORTTED_IN_WINDOWS, "Operation not supported in windows") +TAOS_DEFINE_ERROR(TSDB_CODE_TSC_INVALID_TOTP_CODE, "Invalid TOTP code") TAOS_DEFINE_ERROR(TSDB_CODE_TSC_INTERNAL_ERROR, "Internal error") @@ -235,7 +236,7 @@ TAOS_DEFINE_ERROR(TSDB_CODE_MND_INVALID_ALTER_OPER, "Invalid alter operati TAOS_DEFINE_ERROR(TSDB_CODE_MND_AUTH_FAILURE, "Authentication failure") TAOS_DEFINE_ERROR(TSDB_CODE_MND_PRIVILEDGE_EXIST, "User already have this priviledge") TAOS_DEFINE_ERROR(TSDB_CODE_MND_USER_PASSWORD_REUSE, "Password reuse detected") -//TAOS_DEFINE_ERROR(TSDB_CODE_MND_USER_TIME_RANGE_CONFLICT, "Date time black/white list conflict") +TAOS_DEFINE_ERROR(TSDB_CODE_MND_WRONG_TOTP_CODE, "Wrong TOTP code") TAOS_DEFINE_ERROR(TSDB_CODE_MND_TOO_MANY_USER_IP_RANGE, "Too many ranges in IP white list") TAOS_DEFINE_ERROR(TSDB_CODE_MND_TOO_MANY_USER_TIME_RANGE, "Too many ranges in date time white list") diff --git a/source/util/src/totp.c b/source/util/src/totp.c new file mode 100644 index 00000000000..8b8a487d4f2 --- /dev/null +++ b/source/util/src/totp.c @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2019 TAOS Data, Inc. + * + * This program is free software: you can use, redistribute, and/or modify + * it under the terms of the GNU Affero General Public License, version 3 + * or later ("AGPL"), as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#define _DEFAULT_SOURCE +#include +#include +#include +#include +#include +#include +#include "ttime.h" + +#ifdef WINDOWS + +static int32_t doGenerateTotp(const uint8_t *secret, size_t secretLen, uint64_t tm, int digits) { + return -1; +} + +int taosGenerateTotpSecret(const char *seed, size_t seedLen, uint8_t *secret, size_t secretLen) { + return 0; +} + +#else // WINDOWS + +// for OpenSSL HMAC functions +#include +#include + +// doGenerateTotp generates a TOTP code based on the provided secret and time. +// secret is a byte array, not a base32 string +static int32_t doGenerateTotp(const uint8_t *secret, size_t secretLen, uint64_t tm, int digits) { + if (secretLen < 16) { + return -1; // secret is too short + } + + // convert timestamp to big-endian if system is little-endian + uint32_t endianness = 0xdeadbeef; + if ((*(const uint8_t *)&endianness) == 0xef) { + tm = ((tm & 0x00000000ffffffff) << 32) | ((tm & 0xffffffff00000000) >> 32); + tm = ((tm & 0x0000ffff0000ffff) << 16) | ((tm & 0xffff0000ffff0000) >> 16); + tm = ((tm & 0x00ff00ff00ff00ff) << 8) | ((tm & 0xff00ff00ff00ff00) >> 8); + }; + + // calculate HMAC-SHA1 + uint8_t hmacResult[EVP_MAX_MD_SIZE]; + unsigned int hmacLen; + if (HMAC(EVP_sha1(), secret, secretLen, (const uint8_t *)&tm, sizeof(tm), hmacResult, &hmacLen) == NULL) { + return -1; // HMAC calculation failed + } + + // use the lower 4 bits of the last byte as offset + int offset = hmacResult[hmacLen - 1] & 0x0F; + + int32_t binary = (hmacResult[offset] & 0x7F) << 24; + binary |= (hmacResult[offset + 1] & 0xFF) << 16; + binary |= (hmacResult[offset + 2] & 0xFF) << 8; + binary |= hmacResult[offset + 3] & 0xFF; + + return binary % (int32_t)pow(10, digits); +} + + +// generate TOTP secret from seed +// secret is a byte array, not a base32 string +int taosGenerateTotpSecret(const char *seed, size_t seedLen, uint8_t *secret, size_t secretLen) { + // TOTP secret requires at least 16 characters for adequate security + if (secretLen < 16) { + return 0; + } + + if (seedLen == 0) { + seedLen = strlen(seed); + } + + // calculate HMAC-SHA1 + uint8_t hmacResult[EVP_MAX_MD_SIZE]; + unsigned int hmacLen; + if (HMAC(EVP_sha256(), NULL, 0, seed, seedLen, hmacResult, &hmacLen) == NULL) { + return 0; // HMAC calculation failed + } + + if (hmacLen > secretLen) { + hmacLen = secretLen; + } + (void)memcpy(secret, hmacResult, hmacLen); + + return hmacLen; +} + +#endif // WINDOWS + + +// formatTotp formats the TOTP code with leading zeros to match the specified digit length. +int taosFormatTotp(int32_t totp, int digits, char *buffer, size_t size) { + return snprintf(buffer, size, "%0*d", digits, totp); +} + +// generate TOTP code for given secret at current time +// secret is a byte array, not a base32 string +int32_t taosGenerateTotpCode(const uint8_t *secret, size_t secretLen, int digits) { + if (secretLen < 16) { + return -1; // secret is too short + } + + uint64_t now = taosGetTimestampSec() / 30; + return doGenerateTotp(secret, secretLen, now, digits); +} + +// verify TOTP code for given secret at current time with allowed window +// secret is a byte array, not a base32 string +int taosVerifyTotpCode(const uint8_t *secret, size_t secretLen, int32_t userCode, int digits, int window) { + if (secretLen < 16) { + return 0; // secret is too short + } + + uint64_t now = taosGetTimestampSec() / 30; + for (int i = -window; i <= window; i++) { + if (doGenerateTotp(secret, secretLen, now + i, digits) == userCode) { + return 1; + } + } + + return 0; +} diff --git a/source/util/src/tutil.c b/source/util/src/tutil.c index e648fbaf8c6..58fd86b02b6 100644 --- a/source/util/src/tutil.c +++ b/source/util/src/tutil.c @@ -588,6 +588,29 @@ bool taosIsSpecialChar(char c) { } } +// check if the string is a complex string, a complex string contains +// at least 3 types of characters: upper, lower, digit, special +bool taosIsComplexString(const char* str) { + int hasUpper = 0, hasLower = 0, hasDigit = 0, hasSpecial = 0; + + for (char c = *str; c != 0; c = *(++str)) { + if (taosIsBigChar(c)) { + hasUpper = 1; + } else if (taosIsSmallChar(c)) { + hasLower = 1; + } else if (taosIsNumberChar(c)) { + hasDigit = 1; + } else if (taosIsSpecialChar(c)) { + hasSpecial = 1; + } else { + return false; + } + } + + return (hasUpper + hasLower + hasDigit + hasSpecial) >= 3; +} + + void tTrimMountPrefix(char *fullName) { if (fullName == NULL) { return; diff --git a/source/util/test/errorCodeTable.ini b/source/util/test/errorCodeTable.ini index d4b7d350f23..b48dcacdea6 100644 --- a/source/util/test/errorCodeTable.ini +++ b/source/util/test/errorCodeTable.ini @@ -100,6 +100,7 @@ TSDB_CODE_TSC_ENCODE_PARAM_NULL = 0x80000232 TSDB_CODE_TSC_COMPRESS_PARAM_ERROR = 0x80000233 TSDB_CODE_TSC_COMPRESS_LEVEL_ERROR = 0x80000234 TSDB_CODE_TSC_STMT_BIND_NUMBER_ERROR = 0x80000236 +TSDB_CODE_TSC_INVALID_TOTP_CODE = 0x80000238 TSDB_CODE_TSC_INTERNAL_ERROR = 0x800002FF TSDB_CODE_MND_REQ_REJECTED = 0x80000300 TSDB_CODE_MND_NO_RIGHTS = 0x80000303 @@ -145,6 +146,7 @@ TSDB_CODE_MND_INVALID_ALTER_OPER = 0x80000356 TSDB_CODE_MND_AUTH_FAILURE = 0x80000357 TSDB_CODE_MND_PRIVILEDGE_EXIST = 0x80000359 TSDB_CODE_MND_USER_PASSWORD_REUSE = 0x8000035A +TSDB_CODE_MND_WRONG_TOTP_CODE = 0x8000035B TSDB_CODE_MND_TOO_MANY_USER_IP_RANGE = 0x8000035C TSDB_CODE_MND_TOO_MANY_USER_TIME_RANGE = 0x8000035D TSDB_CODE_MND_STB_ALREADY_EXIST = 0x80000360 diff --git a/test/cases/81-Tools/01-Check/test_check_error_code.py b/test/cases/81-Tools/01-Check/test_check_error_code.py index eeb8688bf7c..ff228848213 100644 --- a/test/cases/81-Tools/01-Check/test_check_error_code.py +++ b/test/cases/81-Tools/01-Check/test_check_error_code.py @@ -20,7 +20,7 @@ ignoreCodes = [ '0x80000129', '0x8000012C', '0x8000012D', '0x8000012E', '0x8000012F', '0x80000136', '0x80000137', '0x80000138', '0x80000139', '0x8000013A', '0x8000013B', '0x80000200', '0x80000201', '0x80000202', '0x80000203', '0x80000204', '0x80000205', '0x80000206', '0x8000020B', '0x8000020E', '0x80000210', '0x80000212', '0x80000214', '0x80000215', '0x80000217', '0x8000021B', '0x8000021C', '0x8000021D', '0x8000021E', '0x80000220', - '0x80000221', '0x8000022C', '0x80000232', '0x80000233', '0x80000234', '0x80000235', '0x80000236', '0x800002FF', '0x80000300', '0x80000316', + '0x80000221', '0x8000022C', '0x80000231', '0x80000232', '0x80000233', '0x80000234', '0x80000235', '0x80000236', '0x800002FF', '0x80000300', '0x80000316', '0x80000317', '0x80000338', '0x80000339', '0x8000033F', '0x80000343', '0x80000345', '0x80000356', '0x80000359', '0x8000035A', '0x8000035B', '0x8000035C', '0x8000035D', '0x80000362', '0x8000038C', '0x8000038E', '0x8000039B', '0x8000039C', '0x8000039D', '0x8000039E', '0x800003A6', '0x800003A7', '0x800003AA', '0x800003AB', '0x800003AC', '0x800003AD', '0x800003B0', '0x800003B2', '0x800003B4', '0x800003B5', '0x800003BA',