I2PControl.cpp
1 /* 2 * Copyright (c) 2013-2025, The PurpleI2P Project 3 * 4 * This file is part of Purple i2pd project and licensed under BSD3 5 * 6 * See full license text in LICENSE file at top of project tree 7 */ 8 9 #include <stdio.h> 10 #include <sstream> 11 #include <iomanip> 12 #include <openssl/x509.h> 13 #include <openssl/pem.h> 14 15 // Use global placeholders from boost introduced when local_time.hpp is loaded 16 #define BOOST_BIND_GLOBAL_PLACEHOLDERS 17 #include <boost/property_tree/json_parser.hpp> 18 #include <boost/algorithm/string.hpp> 19 20 #include "FS.h" 21 #include "Log.h" 22 #include "Config.h" 23 #include "NetDb.hpp" 24 #include "Tunnel.h" 25 #include "Daemon.h" 26 #include "I2PControl.h" 27 28 namespace i2p 29 { 30 namespace client 31 { 32 I2PControlService::I2PControlService (const std::string& address, int port): 33 m_IsRunning (false), 34 m_SSLContext (boost::asio::ssl::context::sslv23), 35 m_ShutdownTimer (m_Service) 36 { 37 if (port) 38 m_Acceptor = std::make_unique<boost::asio::ip::tcp::acceptor>(m_Service, 39 boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address(address), port)); 40 else 41 #if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) 42 { 43 std::remove (address.c_str ()); // just in case 44 m_LocalAcceptor = std::make_unique<boost::asio::local::stream_protocol::acceptor>(m_Service, 45 boost::asio::local::stream_protocol::endpoint(address)); 46 } 47 #else 48 LogPrint(eLogError, "I2PControl: Local sockets are not supported"); 49 #endif 50 51 i2p::config::GetOption("i2pcontrol.password", m_Password); 52 53 // certificate / keys 54 std::string i2pcp_crt; i2p::config::GetOption("i2pcontrol.cert", i2pcp_crt); 55 std::string i2pcp_key; i2p::config::GetOption("i2pcontrol.key", i2pcp_key); 56 57 if (i2pcp_crt.at(0) != '/') 58 i2pcp_crt = i2p::fs::DataDirPath(i2pcp_crt); 59 if (i2pcp_key.at(0) != '/') 60 i2pcp_key = i2p::fs::DataDirPath(i2pcp_key); 61 if (!i2p::fs::Exists (i2pcp_crt) || !i2p::fs::Exists (i2pcp_key)) 62 { 63 LogPrint (eLogInfo, "I2PControl: Creating new certificate for control connection"); 64 CreateCertificate (i2pcp_crt.c_str(), i2pcp_key.c_str()); 65 } 66 else 67 LogPrint(eLogDebug, "I2PControl: Using cert from ", i2pcp_crt); 68 m_SSLContext.set_options (boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::single_dh_use); 69 boost::system::error_code ec; 70 m_SSLContext.use_certificate_file (i2pcp_crt, boost::asio::ssl::context::pem, ec); 71 if (!ec) 72 m_SSLContext.use_private_key_file (i2pcp_key, boost::asio::ssl::context::pem, ec); 73 if (ec) 74 { 75 LogPrint (eLogInfo, "I2PControl: Failed to load ceritifcate: ", ec.message (), ". Recreating"); 76 CreateCertificate (i2pcp_crt.c_str(), i2pcp_key.c_str()); 77 m_SSLContext.use_certificate_file (i2pcp_crt, boost::asio::ssl::context::pem, ec); 78 if (!ec) 79 m_SSLContext.use_private_key_file (i2pcp_key, boost::asio::ssl::context::pem, ec); 80 if (ec) 81 // give up 82 LogPrint (eLogError, "I2PControl: Can't load certificates"); 83 } 84 85 // handlers 86 m_MethodHandlers["Authenticate"] = &I2PControlService::AuthenticateHandler; 87 m_MethodHandlers["Echo"] = &I2PControlService::EchoHandler; 88 m_MethodHandlers["I2PControl"] = &I2PControlService::I2PControlHandler; 89 m_MethodHandlers["RouterInfo"] = &I2PControlHandlers::RouterInfoHandler; 90 m_MethodHandlers["RouterManager"] = &I2PControlService::RouterManagerHandler; 91 m_MethodHandlers["NetworkSetting"] = &I2PControlHandlers::NetworkSettingHandler; 92 m_MethodHandlers["ClientServicesInfo"] = &I2PControlHandlers::ClientServicesInfoHandler; 93 94 // I2PControl 95 m_I2PControlHandlers["i2pcontrol.password"] = &I2PControlService::PasswordHandler; 96 97 // RouterManager 98 m_RouterManagerHandlers["Reseed"] = &I2PControlService::ReseedHandler; 99 m_RouterManagerHandlers["Shutdown"] = &I2PControlService::ShutdownHandler; 100 m_RouterManagerHandlers["ShutdownGraceful"] = &I2PControlService::ShutdownGracefulHandler; 101 } 102 103 I2PControlService::~I2PControlService () 104 { 105 Stop (); 106 } 107 108 void I2PControlService::Start () 109 { 110 if (!m_IsRunning) 111 { 112 Accept (); 113 m_IsRunning = true; 114 m_Thread = std::make_unique<std::thread>(std::bind (&I2PControlService::Run, this)); 115 } 116 } 117 118 void I2PControlService::Stop () 119 { 120 if (m_IsRunning) 121 { 122 m_IsRunning = false; 123 if (m_Acceptor) m_Acceptor->cancel (); 124 #if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) 125 if (m_LocalAcceptor) 126 { 127 auto path = m_LocalAcceptor->local_endpoint().path(); 128 m_LocalAcceptor->cancel (); 129 std::remove (path.c_str ()); 130 } 131 #endif 132 m_Service.stop (); 133 if (m_Thread) 134 { 135 m_Thread->join (); 136 m_Thread = nullptr; 137 } 138 } 139 } 140 141 void I2PControlService::Run () 142 { 143 i2p::util::SetThreadName("I2PC"); 144 145 while (m_IsRunning) 146 { 147 try { 148 m_Service.run (); 149 } catch (std::exception& ex) { 150 LogPrint (eLogError, "I2PControl: Runtime exception: ", ex.what ()); 151 } 152 } 153 } 154 155 void I2PControlService::Accept () 156 { 157 if (m_Acceptor) 158 { 159 auto newSocket = std::make_shared<boost::asio::ssl::stream<boost::asio::ip::tcp::socket> > (m_Service, m_SSLContext); 160 m_Acceptor->async_accept (newSocket->lowest_layer(), 161 [this, newSocket](const boost::system::error_code& ecode) 162 { 163 HandleAccepted (ecode, newSocket); 164 }); 165 } 166 #if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) 167 else if (m_LocalAcceptor) 168 { 169 auto newSocket = std::make_shared<boost::asio::ssl::stream<boost::asio::local::stream_protocol::socket> > (m_Service, m_SSLContext); 170 m_LocalAcceptor->async_accept (newSocket->lowest_layer(), 171 [this, newSocket](const boost::system::error_code& ecode) 172 { 173 HandleAccepted (ecode, newSocket); 174 }); 175 } 176 #endif 177 } 178 179 template<typename ssl_socket> 180 void I2PControlService::HandleAccepted (const boost::system::error_code& ecode, 181 std::shared_ptr<ssl_socket> newSocket) 182 { 183 if (ecode != boost::asio::error::operation_aborted) 184 Accept (); 185 186 if (ecode) 187 { 188 LogPrint (eLogError, "I2PControl: Accept error: ", ecode.message ()); 189 return; 190 } 191 LogPrint (eLogDebug, "I2PControl: New request from ", newSocket->lowest_layer ().remote_endpoint ()); 192 Handshake (newSocket); 193 } 194 195 template<typename ssl_socket> 196 void I2PControlService::Handshake (std::shared_ptr<ssl_socket> socket) 197 { 198 socket->async_handshake(boost::asio::ssl::stream_base::server, 199 [this, socket](const boost::system::error_code& ecode) 200 { 201 if (ecode) 202 { 203 LogPrint (eLogError, "I2PControl: Handshake error: ", ecode.message ()); 204 return; 205 } 206 ReadRequest (socket); 207 }); 208 } 209 210 template<typename ssl_socket> 211 void I2PControlService::ReadRequest (std::shared_ptr<ssl_socket> socket) 212 { 213 auto request = std::make_shared<I2PControlBuffer>(); 214 socket->async_read_some ( 215 #if defined(BOOST_ASIO_HAS_STD_ARRAY) 216 boost::asio::buffer (*request), 217 #else 218 boost::asio::buffer (request->data (), request->size ()), 219 #endif 220 [this, socket, request](const boost::system::error_code& ecode, size_t bytes_transferred) 221 { 222 HandleRequestReceived (ecode, bytes_transferred, socket, request); 223 }); 224 } 225 226 template<typename ssl_socket> 227 void I2PControlService::HandleRequestReceived (const boost::system::error_code& ecode, 228 size_t bytes_transferred, std::shared_ptr<ssl_socket> socket, 229 std::shared_ptr<I2PControlBuffer> buf) 230 { 231 if (ecode) 232 { 233 LogPrint (eLogError, "I2PControl: Read error: ", ecode.message ()); 234 return; 235 } 236 else 237 { 238 bool isHtml = !memcmp (buf->data (), "POST", 4); 239 try 240 { 241 std::stringstream ss; 242 ss.write (buf->data (), bytes_transferred); 243 if (isHtml) 244 { 245 std::string header; 246 size_t contentLength = 0; 247 while (!ss.eof () && header != "\r") 248 { 249 std::getline(ss, header); 250 auto colon = header.find (':'); 251 if (colon != std::string::npos && boost::iequals (header.substr (0, colon), "Content-Length")) 252 contentLength = std::stoi (header.substr (colon + 1)); 253 } 254 if (ss.eof ()) 255 { 256 LogPrint (eLogError, "I2PControl: Malformed request, HTTP header expected"); 257 return; // TODO: 258 } 259 std::streamoff rem = contentLength + ss.tellg () - bytes_transferred; // more bytes to read 260 if (rem > 0) 261 { 262 bytes_transferred = boost::asio::read (*socket, boost::asio::buffer (buf->data (), rem)); 263 ss.write (buf->data (), bytes_transferred); 264 } 265 } 266 std::ostringstream response; 267 boost::property_tree::ptree pt; 268 boost::property_tree::read_json (ss, pt); 269 270 std::string id = pt.get<std::string>("id"); 271 std::string method = pt.get<std::string>("method"); 272 auto it = m_MethodHandlers.find (method); 273 if (it != m_MethodHandlers.end ()) 274 { 275 response << "{\"id\":" << id << ",\"result\":{"; 276 (this->*(it->second))(pt.get_child ("params"), response); 277 response << "},\"jsonrpc\":\"2.0\"}\n"; 278 } 279 else 280 { 281 LogPrint (eLogWarning, "I2PControl: Unknown method ", method); 282 response << "{\"id\":null,\"error\":"; 283 response << "{\"code\":-32601,\"message\":\"Method not found\"},"; 284 response << "\"jsonrpc\":\"2.0\"}\n"; 285 } 286 SendResponse (socket, buf, response, isHtml); 287 } 288 catch (std::exception& ex) 289 { 290 LogPrint (eLogError, "I2PControl: Exception when handle request: ", ex.what ()); 291 std::ostringstream response; 292 response << "{\"id\":null,\"error\":"; 293 response << "{\"code\":-32700,\"message\":\"" << ex.what () << "\"},"; 294 response << "\"jsonrpc\":\"2.0\"}\n"; 295 SendResponse (socket, buf, response, isHtml); 296 } 297 catch (...) 298 { 299 LogPrint (eLogError, "I2PControl: Handle request unknown exception"); 300 } 301 } 302 } 303 304 template<typename ssl_socket> 305 void I2PControlService::SendResponse (std::shared_ptr<ssl_socket> socket, 306 std::shared_ptr<I2PControlBuffer> buf, std::ostringstream& response, bool isHtml) 307 { 308 size_t len = response.str ().length (), offset = 0; 309 if (isHtml) 310 { 311 std::ostringstream header; 312 header << "HTTP/1.1 200 OK\r\n"; 313 header << "Connection: close\r\n"; 314 header << "Content-Length: " << std::to_string(len) << "\r\n"; 315 header << "Content-Type: application/json\r\n"; 316 header << "Date: "; 317 std::time_t t = std::time (nullptr); 318 std::tm tm = *std::gmtime (&t); 319 header << std::put_time(&tm, "%a, %d %b %Y %T GMT") << "\r\n"; 320 header << "\r\n"; 321 offset = header.str ().size (); 322 memcpy (buf->data (), header.str ().c_str (), offset); 323 } 324 memcpy (buf->data () + offset, response.str ().c_str (), len); 325 boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), offset + len), 326 boost::asio::transfer_all (), 327 [socket, buf](const boost::system::error_code& ecode, std::size_t bytes_transferred) 328 { 329 if (ecode) 330 LogPrint (eLogError, "I2PControl: Write error: ", ecode.message ()); 331 }); 332 } 333 334 // handlers 335 336 void I2PControlService::AuthenticateHandler (const boost::property_tree::ptree& params, std::ostringstream& results) 337 { 338 int api = params.get<int> ("API"); 339 auto password = params.get<std::string> ("Password"); 340 LogPrint (eLogDebug, "I2PControl: Authenticate API=", api, " Password=", password); 341 if (password != m_Password) { 342 LogPrint (eLogError, "I2PControl: Authenticate - Invalid password: ", password); 343 return; 344 } 345 InsertParam (results, "API", api); 346 results << ","; 347 std::string token = boost::lexical_cast<std::string>(i2p::util::GetSecondsSinceEpoch ()); 348 m_Tokens.insert (token); 349 InsertParam (results, "Token", token); 350 } 351 352 void I2PControlService::EchoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) 353 { 354 auto echo = params.get<std::string> ("Echo"); 355 LogPrint (eLogDebug, "I2PControl Echo Echo=", echo); 356 InsertParam (results, "Result", echo); 357 } 358 359 360 // I2PControl 361 362 void I2PControlService::I2PControlHandler (const boost::property_tree::ptree& params, std::ostringstream& results) 363 { 364 for (auto& it: params) 365 { 366 LogPrint (eLogDebug, "I2PControl: I2PControl request: ", it.first); 367 auto it1 = m_I2PControlHandlers.find (it.first); 368 if (it1 != m_I2PControlHandlers.end ()) 369 { 370 (this->*(it1->second))(it.second.data ()); 371 InsertParam (results, it.first, ""); 372 } 373 else 374 LogPrint (eLogError, "I2PControl: I2PControl unknown request: ", it.first); 375 } 376 } 377 378 void I2PControlService::PasswordHandler (const std::string& value) 379 { 380 LogPrint (eLogWarning, "I2PControl: New password=", value, ", to make it persistent you should update your config!"); 381 m_Password = value; 382 m_Tokens.clear (); 383 } 384 385 386 // RouterManager 387 388 void I2PControlService::RouterManagerHandler (const boost::property_tree::ptree& params, std::ostringstream& results) 389 { 390 for (auto it = params.begin (); it != params.end (); it++) 391 { 392 LogPrint (eLogDebug, "I2PControl: RouterManager request: ", it->first); 393 auto it1 = m_RouterManagerHandlers.find (it->first); 394 if (it1 != m_RouterManagerHandlers.end ()) 395 { 396 if (it != params.begin ()) results << ","; 397 (this->*(it1->second))(results); 398 } else 399 LogPrint (eLogError, "I2PControl: RouterManager unknown request: ", it->first); 400 } 401 } 402 403 404 void I2PControlService::ShutdownHandler (std::ostringstream& results) 405 { 406 LogPrint (eLogInfo, "I2PControl: Shutdown requested"); 407 InsertParam (results, "Shutdown", ""); 408 m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(1)); // 1 second to make sure response has been sent 409 m_ShutdownTimer.async_wait ( 410 [](const boost::system::error_code& ecode) 411 { 412 Daemon.running = 0; 413 }); 414 } 415 416 void I2PControlService::ShutdownGracefulHandler (std::ostringstream& results) 417 { 418 i2p::context.SetAcceptsTunnels (false); 419 int timeout = i2p::tunnel::tunnels.GetTransitTunnelsExpirationTimeout (); 420 LogPrint (eLogInfo, "I2PControl: Graceful shutdown requested, ", timeout, " seconds remains"); 421 InsertParam (results, "ShutdownGraceful", ""); 422 m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(timeout + 1)); // + 1 second 423 m_ShutdownTimer.async_wait ( 424 [](const boost::system::error_code& ecode) 425 { 426 Daemon.running = 0; 427 }); 428 } 429 430 void I2PControlService::ReseedHandler (std::ostringstream& results) 431 { 432 LogPrint (eLogInfo, "I2PControl: Reseed requested"); 433 InsertParam (results, "Reseed", ""); 434 i2p::data::netdb.Reseed (); 435 } 436 437 // certificate 438 void I2PControlService::CreateCertificate (const char *crt_path, const char *key_path) 439 { 440 FILE *f = NULL; 441 #if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 442 EVP_PKEY * pkey = EVP_RSA_gen(4096); // e = 65537 443 #else 444 EVP_PKEY * pkey = EVP_PKEY_new (); 445 RSA * rsa = RSA_new (); 446 BIGNUM * e = BN_dup (i2p::crypto::GetRSAE ()); 447 RSA_generate_key_ex (rsa, 4096, e, NULL); 448 BN_free (e); 449 if (rsa) EVP_PKEY_assign_RSA (pkey, rsa); 450 else 451 { 452 LogPrint (eLogError, "I2PControl: Can't create RSA key for certificate"); 453 EVP_PKEY_free (pkey); 454 return; 455 } 456 #endif 457 X509 * x509 = X509_new (); 458 ASN1_INTEGER_set (X509_get_serialNumber (x509), 1); 459 X509_gmtime_adj (X509_getm_notBefore (x509), 0); 460 X509_gmtime_adj (X509_getm_notAfter (x509), I2P_CONTROL_CERTIFICATE_VALIDITY*24*60*60); // expiration 461 X509_set_pubkey (x509, pkey); // public key 462 X509_NAME * name = X509_get_subject_name (x509); 463 X509_NAME_add_entry_by_txt (name, "C", MBSTRING_ASC, (unsigned char *)"A1", -1, -1, 0); // country (Anonymous proxy) 464 X509_NAME_add_entry_by_txt (name, "O", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_ORGANIZATION, -1, -1, 0); // organization 465 X509_NAME_add_entry_by_txt (name, "CN", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_COMMON_NAME, -1, -1, 0); // common name 466 X509_set_issuer_name (x509, name); // set issuer to ourselves 467 X509_sign (x509, pkey, EVP_sha1 ()); // sign, last param must be NULL for EdDSA 468 469 // save cert 470 if ((f = fopen (crt_path, "wb")) != NULL) 471 { 472 LogPrint (eLogInfo, "I2PControl: Saving new cert to ", crt_path); 473 PEM_write_X509 (f, x509); 474 fclose (f); 475 } 476 else 477 LogPrint (eLogError, "I2PControl: Can't write cert: ", strerror(errno)); 478 X509_free (x509); 479 480 // save key 481 if ((f = fopen (key_path, "wb")) != NULL) 482 { 483 LogPrint (eLogInfo, "I2PControl: saving cert key to ", key_path); 484 PEM_write_PrivateKey (f, pkey, NULL, NULL, 0, NULL, NULL); 485 fclose (f); 486 } 487 else 488 LogPrint (eLogError, "I2PControl: Can't write key: ", strerror(errno)); 489 EVP_PKEY_free (pkey); 490 } 491 } 492 }