diff --git a/contrib/epee/include/net/http_base.h b/contrib/epee/include/net/http_base.h index 2a227cc70..4ff74fe27 100644 --- a/contrib/epee/include/net/http_base.h +++ b/contrib/epee/include/net/http_base.h @@ -98,6 +98,7 @@ namespace net_utils std::string m_content_encoding; //"Content-Encoding:" std::string m_host; //"Host:" std::string m_cookie; //"Cookie:" + std::string m_user_agent; //"User-Agent:" fields_list m_etc_fields; void clear() @@ -110,6 +111,7 @@ namespace net_utils m_content_encoding.clear(); m_host.clear(); m_cookie.clear(); + m_user_agent.clear(); m_etc_fields.clear(); } }; diff --git a/contrib/epee/include/net/http_client.h b/contrib/epee/include/net/http_client.h index 077c80230..3e8143738 100644 --- a/contrib/epee/include/net/http_client.h +++ b/contrib/epee/include/net/http_client.h @@ -638,10 +638,10 @@ using namespace std; LOG_FRAME("http_stream_filter::parse_cached_header(*)", LOG_LEVEL_4); STATIC_REGEXP_EXPR_1(rexp_mach_field, - "\n?((Connection)|(Referer)|(Content-Length)|(Content-Type)|(Transfer-Encoding)|(Content-Encoding)|(Host)|(Cookie)" - // 12 3 4 5 6 7 8 9 + "\n?((Connection)|(Referer)|(Content-Length)|(Content-Type)|(Transfer-Encoding)|(Content-Encoding)|(Host)|(Cookie)|(User-Agent)" + // 12 3 4 5 6 7 8 9 10 "|([\\w-]+?)) ?: ?((.*?)(\r?\n))[^\t ]", - //10 1112 13 + //11 1213 14 boost::regex::icase | boost::regex::normal); boost::smatch result; @@ -653,8 +653,8 @@ using namespace std; //lookup all fields and fill well-known fields while( boost::regex_search( it_current_bound, it_end_bound, result, rexp_mach_field, boost::match_default) && result[0].matched) { - const size_t field_val = 12; - //const size_t field_etc_name = 10; + const size_t field_val = 13; + //const size_t field_etc_name = 11; int i = 2; //start position = 2 if(result[i++].matched)//"Connection" @@ -675,6 +675,8 @@ using namespace std; } else if(result[i++].matched)//"Cookie" body_info.m_cookie = result[field_val]; + else if(result[i++].matched)//"User-Agent" + body_info.m_user_agent = result[field_val]; else if(result[i++].matched)//e.t.c (HAVE TO BE MATCHED!) {;} else diff --git a/contrib/epee/include/net/http_protocol_handler.h b/contrib/epee/include/net/http_protocol_handler.h index aed909778..40e3392db 100644 --- a/contrib/epee/include/net/http_protocol_handler.h +++ b/contrib/epee/include/net/http_protocol_handler.h @@ -49,6 +49,7 @@ namespace net_utils struct http_server_config { std::string m_folder; + std::string m_required_user_agent; critical_section m_lock; }; diff --git a/contrib/epee/include/net/http_protocol_handler.inl b/contrib/epee/include/net/http_protocol_handler.inl index 2458af047..6beff0109 100644 --- a/contrib/epee/include/net/http_protocol_handler.inl +++ b/contrib/epee/include/net/http_protocol_handler.inl @@ -285,7 +285,8 @@ namespace net_utils } break; } - analize_cached_request_header_and_invoke_state(pos); + if (!analize_cached_request_header_and_invoke_state(pos)) + return false; break; } case http_state_retriving_body: @@ -387,8 +388,16 @@ namespace net_utils { LOG_ERROR("simple_http_connection_handler::analize_cached_request_header_and_invoke_state(): failed to anilize request header: " << m_cache); m_state = http_state_error; + return false; } + if (!m_config.m_required_user_agent.empty() && m_query_info.m_header_info.m_user_agent != m_config.m_required_user_agent) + { + LOG_ERROR("simple_http_connection_handler::analize_cached_request_header_and_invoke_state(): unexpected user agent: " << m_query_info.m_header_info.m_user_agent); + m_state = http_state_error; + return false; + } + m_cache.erase(0, pos); std::string req_command_str = m_query_info.m_full_request_str; @@ -473,10 +482,10 @@ namespace net_utils LOG_FRAME("http_stream_filter::parse_cached_header(*)", LOG_LEVEL_3); STATIC_REGEXP_EXPR_1(rexp_mach_field, - "\n?((Connection)|(Referer)|(Content-Length)|(Content-Type)|(Transfer-Encoding)|(Content-Encoding)|(Host)|(Cookie)" - // 12 3 4 5 6 7 8 9 + "\n?((Connection)|(Referer)|(Content-Length)|(Content-Type)|(Transfer-Encoding)|(Content-Encoding)|(Host)|(Cookie)|(User-Agent)" + // 12 3 4 5 6 7 8 9 10 "|([\\w-]+?)) ?: ?((.*?)(\r?\n))[^\t ]", - //10 1112 13 + //11 1213 14 boost::regex::icase | boost::regex::normal); boost::smatch result; @@ -488,8 +497,8 @@ namespace net_utils //lookup all fields and fill well-known fields while( boost::regex_search( it_current_bound, it_end_bound, result, rexp_mach_field, boost::match_default) && result[0].matched) { - const size_t field_val = 12; - const size_t field_etc_name = 10; + const size_t field_val = 13; + const size_t field_etc_name = 11; int i = 2; //start position = 2 if(result[i++].matched)//"Connection" @@ -508,6 +517,8 @@ namespace net_utils body_info.m_host = result[field_val]; else if(result[i++].matched)//"Cookie" body_info.m_cookie = result[field_val]; + else if(result[i++].matched)//"User-Agent" + body_info.m_user_agent = result[field_val]; else if(result[i++].matched)//e.t.c (HAVE TO BE MATCHED!) body_info.m_etc_fields.push_back(std::pair(result[field_etc_name], result[field_val])); else diff --git a/contrib/epee/include/net/http_server_impl_base.h b/contrib/epee/include/net/http_server_impl_base.h index 10f74b9a8..65fe5eed6 100644 --- a/contrib/epee/include/net/http_server_impl_base.h +++ b/contrib/epee/include/net/http_server_impl_base.h @@ -52,7 +52,7 @@ namespace epee : m_net_server(external_io_service) {} - bool init(const std::string& bind_port = "0", const std::string& bind_ip = "0.0.0.0") + bool init(const std::string& bind_port = "0", const std::string& bind_ip = "0.0.0.0", const std::string &user_agent = "") { //set self as callback handler @@ -61,6 +61,9 @@ namespace epee //here set folder for hosting reqests m_net_server.get_config_object().m_folder = ""; + // workaround till we get auth/encryption + m_net_server.get_config_object().m_required_user_agent = user_agent; + LOG_PRINT_L0("Binding on " << bind_ip << ":" << bind_port); bool res = m_net_server.init_server(bind_port, bind_ip); if(!res) diff --git a/src/common/command_line.cpp b/src/common/command_line.cpp index 90db3b04d..fc096abe5 100644 --- a/src/common/command_line.cpp +++ b/src/common/command_line.cpp @@ -48,6 +48,7 @@ namespace command_line const arg_descriptor arg_version = {"version", "Output version information"}; const arg_descriptor arg_data_dir = {"data-dir", "Specify data directory"}; const arg_descriptor arg_testnet_data_dir = {"testnet-data-dir", "Specify testnet data directory"}; + const arg_descriptor arg_user_agent = {"user-agent", "Restrict RPC use to clients using this user agent"}; const arg_descriptor arg_test_drop_download = {"test-drop-download", "For net tests: in download, discard ALL blocks instead checking/saving them (very fast)"}; const arg_descriptor arg_test_drop_download_height = {"test-drop-download-height", "Like test-drop-download but disards only after around certain height", 0}; const arg_descriptor arg_test_dbg_lock_sleep = {"test-dbg-lock-sleep", "Sleep time in ms, defaults to 0 (off), used to debug before/after locking mutex. Values 100 to 1000 are good for tests."}; diff --git a/src/common/command_line.h b/src/common/command_line.h index 75dc4b9fd..731b8b0bb 100644 --- a/src/common/command_line.h +++ b/src/common/command_line.h @@ -204,6 +204,7 @@ namespace command_line extern const arg_descriptor arg_version; extern const arg_descriptor arg_data_dir; extern const arg_descriptor arg_testnet_data_dir; + extern const arg_descriptor arg_user_agent; extern const arg_descriptor arg_test_drop_download; extern const arg_descriptor arg_test_drop_download_height; extern const arg_descriptor arg_test_dbg_lock_sleep; diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 00ea6ef6c..83892c661 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -34,10 +34,11 @@ namespace daemonize { t_command_parser_executor::t_command_parser_executor( uint32_t ip , uint16_t port + , const std::string &user_agent , bool is_rpc , cryptonote::core_rpc_server* rpc_server ) - : m_executor(ip, port, is_rpc, rpc_server) + : m_executor(ip, port, user_agent, is_rpc, rpc_server) {} bool t_command_parser_executor::print_peer_list(const std::vector& args) diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h index 11df92a5e..e59f51cdb 100644 --- a/src/daemon/command_parser_executor.h +++ b/src/daemon/command_parser_executor.h @@ -49,6 +49,7 @@ public: t_command_parser_executor( uint32_t ip , uint16_t port + , const std::string &user_agent , bool is_rpc , cryptonote::core_rpc_server* rpc_server = NULL ); diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index ce8ac44fc..2c3c54841 100644 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -37,10 +37,11 @@ namespace p = std::placeholders; t_command_server::t_command_server( uint32_t ip , uint16_t port + , const std::string &user_agent , bool is_rpc , cryptonote::core_rpc_server* rpc_server ) - : m_parser(ip, port, is_rpc, rpc_server) + : m_parser(ip, port, user_agent, is_rpc, rpc_server) , m_command_lookup() , m_is_rpc(is_rpc) { diff --git a/src/daemon/command_server.h b/src/daemon/command_server.h index d7022e135..fb1702aae 100644 --- a/src/daemon/command_server.h +++ b/src/daemon/command_server.h @@ -54,6 +54,7 @@ public: t_command_server( uint32_t ip , uint16_t port + , const std::string &user_agent , bool is_rpc = true , cryptonote::core_rpc_server* rpc_server = NULL ); diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index e79823d08..74875bfb0 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -124,7 +124,7 @@ bool t_daemon::run(bool interactive) if (interactive) { - rpc_commands = new daemonize::t_command_server(0, 0, false, mp_internals->rpc.get_server()); + rpc_commands = new daemonize::t_command_server(0, 0, "", false, mp_internals->rpc.get_server()); rpc_commands->start_handling(std::bind(&daemonize::t_daemon::stop_p2p, this)); } diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp index 93027a5d6..0895e1bf1 100644 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -208,6 +208,7 @@ int main(int argc, char const * argv[]) { rpc_port_str = command_line::get_arg(vm, cryptonote::core_rpc_server::arg_testnet_rpc_bind_port); } + auto user_agent = command_line::get_arg(vm, cryptonote::core_rpc_server::arg_user_agent); uint32_t rpc_ip; uint16_t rpc_port; @@ -222,7 +223,7 @@ int main(int argc, char const * argv[]) return 1; } - daemonize::t_command_server rpc_commands{rpc_ip, rpc_port}; + daemonize::t_command_server rpc_commands{rpc_ip, rpc_port, user_agent}; if (rpc_commands.process_command_vec(command)) { return 0; diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index b43e01e1f..ad6041fca 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -92,6 +92,7 @@ namespace { t_rpc_command_executor::t_rpc_command_executor( uint32_t ip , uint16_t port + , const std::string &user_agent , bool is_rpc , cryptonote::core_rpc_server* rpc_server ) diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index 7e73e7faf..afb2b5f36 100644 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -57,6 +57,7 @@ public: t_rpc_command_executor( uint32_t ip , uint16_t port + , const std::string &user_agent , bool is_rpc = true , cryptonote::core_rpc_server* rpc_server = NULL ); diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 5aa3591ab..f6431a018 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -54,6 +54,7 @@ namespace cryptonote command_line::add_arg(desc, arg_rpc_bind_port); command_line::add_arg(desc, arg_testnet_rpc_bind_port); command_line::add_arg(desc, arg_restricted_rpc); + command_line::add_arg(desc, arg_user_agent); } //------------------------------------------------------------------------------------------------------------------------------ core_rpc_server::core_rpc_server( @@ -81,11 +82,12 @@ namespace cryptonote ) { m_testnet = command_line::get_arg(vm, command_line::arg_testnet_on); + std::string m_user_agent = command_line::get_arg(vm, command_line::arg_user_agent); m_net_server.set_threads_prefix("RPC"); bool r = handle_command_line(vm); CHECK_AND_ASSERT_MES(r, false, "Failed to process command line in core_rpc_server"); - return epee::http_server_impl_base::init(m_port, m_bind_ip); + return epee::http_server_impl_base::init(m_port, m_bind_ip, m_user_agent); } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::check_core_busy() @@ -1277,4 +1279,10 @@ namespace cryptonote , false }; + const command_line::arg_descriptor core_rpc_server::arg_user_agent = { + "user-agent" + , "Restrict RPC to clients using this user agent" + , "" + }; + } // namespace cryptonote diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 9fcf3abf8..9885aa0ff 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -56,6 +56,7 @@ namespace cryptonote static const command_line::arg_descriptor arg_rpc_bind_port; static const command_line::arg_descriptor arg_testnet_rpc_bind_port; static const command_line::arg_descriptor arg_restricted_rpc; + static const command_line::arg_descriptor arg_user_agent; typedef epee::net_utils::connection_context_base connection_context; diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 1816ae801..faa40e166 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -45,11 +45,13 @@ namespace tools //----------------------------------------------------------------------------------- const command_line::arg_descriptor wallet_rpc_server::arg_rpc_bind_port = {"rpc-bind-port", "Starts wallet as rpc server for wallet operations, sets bind port for server", "", true}; const command_line::arg_descriptor wallet_rpc_server::arg_rpc_bind_ip = {"rpc-bind-ip", "Specify ip to bind rpc server", "127.0.0.1"}; + const command_line::arg_descriptor wallet_rpc_server::arg_user_agent = {"user-agent", "Restrict RPC to clients using this user agent", ""}; void wallet_rpc_server::init_options(boost::program_options::options_description& desc) { command_line::add_arg(desc, arg_rpc_bind_ip); command_line::add_arg(desc, arg_rpc_bind_port); + command_line::add_arg(desc, arg_user_agent); } //------------------------------------------------------------------------------------------------------------------------------ wallet_rpc_server::wallet_rpc_server(wallet2& w):m_wallet(w) @@ -83,6 +85,7 @@ namespace tools { m_bind_ip = command_line::get_arg(vm, arg_rpc_bind_ip); m_port = command_line::get_arg(vm, arg_rpc_bind_port); + m_user_agent = command_line::get_arg(vm, arg_user_agent); return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -91,7 +94,7 @@ namespace tools m_net_server.set_threads_prefix("RPC"); bool r = handle_command_line(vm); CHECK_AND_ASSERT_MES(r, false, "Failed to process command line in core_rpc_server"); - return epee::http_server_impl_base::init(m_port, m_bind_ip); + return epee::http_server_impl_base::init(m_port, m_bind_ip, m_user_agent); } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_getbalance(const wallet_rpc::COMMAND_RPC_GET_BALANCE::request& req, wallet_rpc::COMMAND_RPC_GET_BALANCE::response& res, epee::json_rpc::error& er) diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index c2532948f..b3e95c18a 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -50,6 +50,7 @@ namespace tools const static command_line::arg_descriptor arg_rpc_bind_port; const static command_line::arg_descriptor arg_rpc_bind_ip; + const static command_line::arg_descriptor arg_user_agent; static void init_options(boost::program_options::options_description& desc); @@ -120,6 +121,7 @@ namespace tools wallet2& m_wallet; std::string m_port; std::string m_bind_ip; + std::string m_user_agent; std::atomic m_stop; }; }