diff --git a/config.example.json b/config.example.json index d6446a0b52..856163db21 100644 --- a/config.example.json +++ b/config.example.json @@ -69,8 +69,10 @@ //auto_batch: this feature is only available for the PostgreSQL driver(version >= 14.0), see //the wiki for more details. "auto_batch": false - //connect_options: extra options for the connection. Only works for PostgreSQL now. - //For more information, see https://www.postgresql.org/docs/16/libpq-connect.html#LIBPQ-CONNECT-OPTIONS + //connect_options: extra options for the connection. + //For PostgreSQL, see https://www.postgresql.org/docs/16/libpq-connect.html#LIBPQ-CONNECT-OPTIONS + //For MySQL, the valid options are:ssl_key, ssl_cert, ssl_ca, ssl_capath, ssl_cipher, see + //https://dev.mysql.com/doc/c-api/8.4/en/mysql-ssl-set.html //"connect_options": { "statement_timeout": "1s" } } ], diff --git a/drogon_ctl/create_model.cc b/drogon_ctl/create_model.cc index 147353c1b6..2026db5c62 100644 --- a/drogon_ctl/create_model.cc +++ b/drogon_ctl/create_model.cc @@ -871,6 +871,20 @@ void create_model::createModel(const std::string &path, connStr += " client_encoding="; connStr += escapeConnString(characterSet); } + auto options = config["connect_options"]; + if (options.isObject() && !options.empty()) + { + std::string optionStr = " options='"; + for (auto const &key : options.getMemberNames()) + { + optionStr += " -c "; + optionStr += escapeConnString(key); + optionStr += "="; + optionStr += escapeConnString(options[key].asString()); + } + optionStr += "'"; + connStr += optionStr; + } auto schema = config.get("schema", "public").asString(); DbClientPtr client = drogon::orm::DbClient::newPgClient(connStr, 1); @@ -983,6 +997,19 @@ void create_model::createModel(const std::string &path, connStr += " client_encoding="; connStr += escapeConnString(characterSet); } + auto options = config["connect_options"]; + if (options.isObject() && !options.empty()) + { + std::string optionStr; + for (auto const &key : options.getMemberNames()) + { + optionStr += " "; + optionStr += escapeConnString(key); + optionStr += "="; + optionStr += escapeConnString(options[key].asString()); + } + connStr += optionStr; + } DbClientPtr client = drogon::orm::DbClient::newMysqlClient(connStr, 1); std::cout << "Connect to server..." << std::endl; if (forceOverwrite_) diff --git a/drogon_ctl/templates/config_json.csp b/drogon_ctl/templates/config_json.csp index a02bba1a12..ab195684aa 100644 --- a/drogon_ctl/templates/config_json.csp +++ b/drogon_ctl/templates/config_json.csp @@ -69,8 +69,10 @@ //auto_batch: this feature is only available for the PostgreSQL driver(version >= 14.0), see //the wiki for more details. "auto_batch": false - //connect_options: extra options for the connection. Only works for PostgreSQL now. - //For more information, see https://www.postgresql.org/docs/16/libpq-connect.html#LIBPQ-CONNECT-OPTIONS + //connect_options: extra options for the connection. + //For PostgreSQL, see https://www.postgresql.org/docs/16/libpq-connect.html#LIBPQ-CONNECT-OPTIONS + //For MySQL, the valid options are:ssl_key, ssl_cert, ssl_ca, ssl_capath, ssl_cipher, see + //https://dev.mysql.com/doc/c-api/8.4/en/mysql-ssl-set.html //"connect_options": { "statement_timeout": "1s" } } ], @@ -108,7 +110,7 @@ "session_timeout": 0, //string value of SameSite attribute of the Set-Cookie HTTP response header //valid value is either 'Null' (default), 'Lax', 'Strict' or 'None' - "session_same_site" : "Null", + "session_same_site": "Null", //session_cookie_key: The cookie key of the session, "JSESSIONID" by default "session_cookie_key": "JSESSIONID", //session_max_age: The max age of the session cookie, -1 by default diff --git a/lib/src/HttpAppFrameworkImpl.cc b/lib/src/HttpAppFrameworkImpl.cc index 35448bad2f..3e74933f56 100644 --- a/lib/src/HttpAppFrameworkImpl.cc +++ b/lib/src/HttpAppFrameworkImpl.cc @@ -994,7 +994,8 @@ void HttpAppFrameworkImpl::addDbClient( name, isFast, characterSet, - timeout}); + timeout, + std::move(options)}); } else if (dbType == "sqlite3") { diff --git a/orm_lib/inc/drogon/orm/DbConfig.h b/orm_lib/inc/drogon/orm/DbConfig.h index 7cbd8f3a6f..b91c8d41fc 100644 --- a/orm_lib/inc/drogon/orm/DbConfig.h +++ b/orm_lib/inc/drogon/orm/DbConfig.h @@ -48,6 +48,7 @@ struct MysqlConfig bool isFast; std::string characterSet; double timeout; + std::unordered_map connectOptions; }; struct Sqlite3Config diff --git a/orm_lib/src/DbClientManager.cc b/orm_lib/src/DbClientManager.cc index 6b92943a8d..d25ef80953 100644 --- a/orm_lib/src/DbClientManager.cc +++ b/orm_lib/src/DbClientManager.cc @@ -215,6 +215,18 @@ void DbClientManager::addDbClient(const DbConfig &config) cfg.username, cfg.password, cfg.characterSet); + if (!cfg.connectOptions.empty()) + { + std::string optionStr; + for (auto const &[key, value] : cfg.connectOptions) + { + optionStr += " "; + optionStr += escapeConnString(key); + optionStr += "="; + optionStr += escapeConnString(value); + } + connStr += optionStr; + } dbInfos_.emplace_back(DbInfo{connStr, config}); #else std::cout << "The Mysql is not supported in current drogon build, " diff --git a/orm_lib/src/mysql_impl/MysqlConnection.cc b/orm_lib/src/mysql_impl/MysqlConnection.cc index fb352feced..a4bae7fda4 100644 --- a/orm_lib/src/mysql_impl/MysqlConnection.cc +++ b/orm_lib/src/mysql_impl/MysqlConnection.cc @@ -59,22 +59,18 @@ MysqlConnection::MysqlConnection(trantor::EventLoop *loop, static MysqlEnv env; static thread_local MysqlThreadEnv threadEnv; mysql_init(mysqlPtr_.get()); - mysql_options(mysqlPtr_.get(), MYSQL_OPT_NONBLOCK, nullptr); -#ifdef HAS_MYSQL_OPTIONSV - mysql_optionsv(mysqlPtr_.get(), MYSQL_OPT_RECONNECT, &reconnect_); -#endif - // Get the key and value + // Parse SSL parameters from connection string + std::string ssl_key, ssl_cert, ssl_ca, ssl_capath, ssl_cipher; auto connParams = parseConnString(connInfo); for (auto const &kv : connParams) { auto key = kv.first; auto value = kv.second; - std::transform(key.begin(), key.end(), key.begin(), [](unsigned char c) { return tolower(c); }); - // LOG_TRACE << key << "=" << value; + LOG_DEBUG << "connInfo key:[" << key << "] value:[" << value << "]"; if (key == "host") { host_ = value; @@ -100,7 +96,46 @@ MysqlConnection::MysqlConnection(trantor::EventLoop *loop, { characterSet_ = value; } + else if (key == "ssl_key") + { + ssl_key = value; + } + else if (key == "ssl_cert") + { + ssl_cert = value; + } + else if (key == "ssl_ca") + { + ssl_ca = value; + } + else if (key == "ssl_capath") + { + ssl_capath = value; + } + else if (key == "ssl_cipher") + { + ssl_cipher = value; + } + } + // If all SSL parameters are empty, log a warning about certificate + // verification + if (ssl_key.empty() && ssl_cert.empty() && ssl_ca.empty() && + ssl_capath.empty() && ssl_cipher.empty()) + { + LOG_DEBUG << "no certificate parameters are set. " + << "This disables certificate verification and may allow " + "man-in-the-middle attacks."; } + mysql_ssl_set(mysqlPtr_.get(), + ssl_key.empty() ? nullptr : ssl_key.c_str(), + ssl_cert.empty() ? nullptr : ssl_cert.c_str(), + ssl_ca.empty() ? nullptr : ssl_ca.c_str(), + ssl_capath.empty() ? nullptr : ssl_capath.c_str(), + ssl_cipher.empty() ? nullptr : ssl_cipher.c_str()); + mysql_options(mysqlPtr_.get(), MYSQL_OPT_NONBLOCK, nullptr); +#ifdef HAS_MYSQL_OPTIONSV + mysql_optionsv(mysqlPtr_.get(), MYSQL_OPT_RECONNECT, &reconnect_); +#endif } void MysqlConnection::init()