#include #include #include namespace FSecure::Sql { inline namespace Handle { namespace Detail { struct SqlHandleDeleter { SqlHandleDeleter(SQLSMALLINT type) noexcept : m_Type{ type } { } void operator()(SQLHANDLE handle) noexcept { SQLFreeHandle(m_Type, handle); } private: SQLSMALLINT m_Type; }; using SqlHandle = std::unique_ptr, SqlHandleDeleter>; SqlHandle MakeSqlHandle(SQLSMALLINT type, SQLHANDLE parent = SQL_NULL_HANDLE) { SQLHANDLE tmp; if (SQLAllocHandle(type, parent, &tmp) == SQL_ERROR) { auto GetTypeString = [](SQLSMALLINT type) { switch (type) { case SQL_HANDLE_ENV: return OBF_STR("ENV"); case SQL_HANDLE_STMT: return OBF_STR("STMT"); case SQL_HANDLE_DBC: return OBF_STR("DBC"); case SQL_HANDLE_DESC: return OBF_STR("DESC"); default: return ""s; } }; throw std::runtime_error(OBF_STR("SQLAllocHandle for failed. handle type: ") + GetTypeString(type)); } return SqlHandle(tmp, { type }); } } class Statement { public: Statement(SQLHANDLE connection, std::string statement) : m_Stmt{ Detail::MakeSqlHandle(SQL_HANDLE_STMT, connection) }, m_StmtString{ std::move(statement) } { if (m_StmtString.size() > std::numeric_limits::max()) throw std::runtime_error{ OBF("Statement too long") }; } void Execute() { SQLRETURN retCode = SQLExecDirectA(m_Stmt.get(), (SQLCHAR*)m_StmtString.c_str(), static_cast(m_StmtString.size())); if (retCode == SQL_ERROR) throw std::runtime_error{ OBF("Failed to execute statement: ") + m_StmtString.substr(0, 80) }; } SQLRETURN Fetch() { return SQLFetch(m_Stmt.get()); } std::string GetString(SQLSMALLINT columnNumber) { std::string out; constexpr auto bufferSize = 4096; SQLLEN dataLen = 0; do { SQLCHAR buf[bufferSize]; auto status = SQLGetData(m_Stmt.get(), columnNumber, SQL_CHAR, buf, bufferSize, &dataLen); if (status == SQL_ERROR) throw std::runtime_error{ OBF("Failed to read data. coulmn number") + std::to_string(columnNumber) }; out += reinterpret_cast(buf); } while (dataLen > bufferSize || dataLen == SQL_NO_TOTAL); return out; } private: Detail::SqlHandle m_Stmt; std::string m_StmtString; }; class Connection { public: Connection(SQLHANDLE env, std::string const& servername, std::string const& databasename, std::string const& username, std::string const& password, bool useSSPI, HANDLE impersonationToken) : m_Connection{ Detail::MakeSqlHandle(SQL_HANDLE_DBC, env) } { std::string connString; if (useSSPI) { if (!username.empty()) { //Sending and recieving could be different threads, have to inject the token per-thread if (!SetThreadToken(NULL, impersonationToken)) throw std::runtime_error("[x] error setting token"); } connString = OBF("DRIVER={SQL Server};SERVER=") + servername + OBF(", 1433;") + OBF("DATABASE=") + databasename + OBF(";Integrated Security=SSPI;"); } else { connString = OBF("DRIVER={SQL Server};SERVER=") + servername + OBF(", 1433;") + OBF("DATABASE=") + databasename + OBF(";UID=") + username + OBF(";PWD=") + password; } if (connString.size() > std::numeric_limits::max()) throw std::runtime_error{ OBF("Connection string too long") }; SQLRETURN retCode = SQLDriverConnectA(m_Connection.get(), NULL, (SQLCHAR*)connString.c_str(), static_cast(connString.size()), nullptr, 0, nullptr, SQL_DRIVER_NOPROMPT); if (retCode == SQL_ERROR) throw std::runtime_error(OBF("[x] Unable to connect to MSSQL database")); } ~Connection() { SQLDisconnect(m_Connection.get()); } Statement MakeStatement(std::string statement) { return Statement(m_Connection.get(), std::move(statement)); } private: Detail::SqlHandle m_Connection; }; class Enviroment { public: Enviroment() : m_Env{ Detail::MakeSqlHandle(SQL_HANDLE_ENV) } { if (SQLSetEnvAttr(m_Env.get(), SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0) != SQL_SUCCESS) throw std::runtime_error(OBF("[x] Error setting enviroment")); } Connection Connect(std::string const& m_servername, std::string const& m_databasename, std::string const& m_username, std::string const& m_password, bool m_useSSPI, HANDLE m_impersonationToken) { return Connection(m_Env.get(), m_servername, m_databasename, m_username, m_password, m_useSSPI, m_impersonationToken); } private: Detail::SqlHandle m_Env; }; } }