/ libi2pd_client / BOB.cpp
BOB.cpp
1 /* 2 * Copyright (c) 2013-2026, 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 <string.h> 10 #include "Log.h" 11 #include "ClientContext.h" 12 #include "util.h" 13 #include "BOB.h" 14 15 namespace i2p 16 { 17 namespace client 18 { 19 void BOBI2PTunnelIncomingConnection::Established () 20 { 21 if (m_IsQuiet) 22 StreamReceive (); 23 else 24 { 25 // send destination first like received from I2P 26 std::string dest = GetStream ()->GetRemoteIdentity ()->ToBase64 (); 27 dest += "\n"; 28 if (dest.size() <= I2P_TUNNEL_CONNECTION_BUFFER_SIZE) 29 memcpy (GetStreamBuffer (), dest.c_str (), dest.size ()); 30 else 31 memset (GetStreamBuffer (), 0, I2P_TUNNEL_CONNECTION_BUFFER_SIZE); 32 HandleStreamReceive (boost::system::error_code (), dest.size ()); 33 } 34 Receive (); 35 } 36 37 BOBI2PInboundTunnel::BOBI2PInboundTunnel (const boost::asio::ip::tcp::endpoint& ep, std::shared_ptr<ClientDestination> localDestination): 38 BOBI2PTunnel (localDestination), m_Acceptor (localDestination->GetService (), ep) 39 { 40 } 41 42 BOBI2PInboundTunnel::~BOBI2PInboundTunnel () 43 { 44 Stop (); 45 } 46 47 void BOBI2PInboundTunnel::Start () 48 { 49 m_Acceptor.listen (); 50 Accept (); 51 } 52 53 void BOBI2PInboundTunnel::Stop () 54 { 55 m_Acceptor.close(); 56 ClearHandlers (); 57 } 58 59 void BOBI2PInboundTunnel::Accept () 60 { 61 auto receiver = std::make_shared<AddressReceiver> (); 62 receiver->socket = std::make_shared<boost::asio::ip::tcp::socket> (GetService ()); 63 m_Acceptor.async_accept (*receiver->socket, std::bind (&BOBI2PInboundTunnel::HandleAccept, this, 64 std::placeholders::_1, receiver)); 65 } 66 67 void BOBI2PInboundTunnel::HandleAccept (const boost::system::error_code& ecode, std::shared_ptr<AddressReceiver> receiver) 68 { 69 if (!ecode) 70 { 71 Accept (); 72 ReceiveAddress (receiver); 73 } 74 } 75 76 void BOBI2PInboundTunnel::ReceiveAddress (std::shared_ptr<AddressReceiver> receiver) 77 { 78 receiver->socket->async_read_some (boost::asio::buffer( 79 receiver->buffer + receiver->bufferOffset, 80 BOB_COMMAND_BUFFER_SIZE - receiver->bufferOffset), 81 std::bind(&BOBI2PInboundTunnel::HandleReceivedAddress, this, 82 std::placeholders::_1, std::placeholders::_2, receiver)); 83 } 84 85 void BOBI2PInboundTunnel::HandleReceivedAddress (const boost::system::error_code& ecode, std::size_t bytes_transferred, 86 std::shared_ptr<AddressReceiver> receiver) 87 { 88 if (ecode) 89 LogPrint (eLogError, "BOB: Inbound tunnel read error: ", ecode.message ()); 90 else 91 { 92 receiver->bufferOffset += bytes_transferred; 93 receiver->buffer[receiver->bufferOffset] = 0; 94 char * eol = strchr (receiver->buffer, '\n'); 95 if (eol) 96 { 97 *eol = 0; 98 if (eol != receiver->buffer && eol[-1] == '\r') eol[-1] = 0; // workaround for Transmission, it sends '\r\n' terminated address 99 receiver->data = (uint8_t *)eol + 1; 100 receiver->dataLen = receiver->bufferOffset - (eol - receiver->buffer + 1); 101 auto addr = context.GetAddressBook ().GetAddress (receiver->buffer); 102 if (!addr) 103 { 104 LogPrint (eLogError, "BOB: Address ", receiver->buffer, " not found"); 105 return; 106 } 107 if (addr->IsIdentHash ()) 108 { 109 auto leaseSet = GetLocalDestination ()->FindLeaseSet (addr->identHash); 110 if (leaseSet) 111 CreateConnection (receiver, leaseSet); 112 else 113 GetLocalDestination ()->RequestDestination (addr->identHash, 114 std::bind (&BOBI2PInboundTunnel::HandleDestinationRequestComplete, 115 this, std::placeholders::_1, receiver)); 116 } 117 else 118 GetLocalDestination ()->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey, 119 std::bind (&BOBI2PInboundTunnel::HandleDestinationRequestComplete, 120 this, std::placeholders::_1, receiver)); 121 } 122 else 123 { 124 if (receiver->bufferOffset < BOB_COMMAND_BUFFER_SIZE) 125 ReceiveAddress (receiver); 126 else 127 LogPrint (eLogError, "BOB: Missing inbound address"); 128 } 129 } 130 } 131 132 void BOBI2PInboundTunnel::HandleDestinationRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet, std::shared_ptr<AddressReceiver> receiver) 133 { 134 if (leaseSet) 135 CreateConnection (receiver, leaseSet); 136 else 137 LogPrint (eLogError, "BOB: LeaseSet for inbound destination not found"); 138 } 139 140 void BOBI2PInboundTunnel::CreateConnection (std::shared_ptr<AddressReceiver> receiver, std::shared_ptr<const i2p::data::LeaseSet> leaseSet) 141 { 142 LogPrint (eLogDebug, "BOB: New inbound connection"); 143 auto connection = std::make_shared<I2PTunnelConnection>(this, receiver->socket, leaseSet); 144 AddHandler (connection); 145 connection->I2PConnect (receiver->data, receiver->dataLen); 146 } 147 148 BOBI2POutboundTunnel::BOBI2POutboundTunnel (const std::string& outhost, uint16_t port, 149 std::shared_ptr<ClientDestination> localDestination, bool quiet): BOBI2PTunnel (localDestination), 150 m_Endpoint (boost::asio::ip::make_address (outhost), port), m_IsQuiet (quiet) 151 { 152 } 153 154 void BOBI2POutboundTunnel::Start () 155 { 156 Accept (); 157 } 158 159 void BOBI2POutboundTunnel::Stop () 160 { 161 ClearHandlers (); 162 } 163 164 void BOBI2POutboundTunnel::Accept () 165 { 166 auto localDestination = GetLocalDestination (); 167 if (localDestination) 168 localDestination->AcceptStreams (std::bind (&BOBI2POutboundTunnel::HandleAccept, this, std::placeholders::_1)); 169 else 170 LogPrint (eLogError, "BOB: Local destination not set for server tunnel"); 171 } 172 173 void BOBI2POutboundTunnel::HandleAccept (std::shared_ptr<i2p::stream::Stream> stream) 174 { 175 if (stream) 176 { 177 auto conn = std::make_shared<BOBI2PTunnelIncomingConnection> (this, stream, m_Endpoint, m_IsQuiet); 178 AddHandler (conn); 179 conn->Connect (); 180 } 181 } 182 183 BOBDestination::BOBDestination (std::shared_ptr<ClientDestination> localDestination, 184 const std::string &nickname, const std::string &inhost, const std::string &outhost, 185 const uint16_t inport, const uint16_t outport, const bool quiet): 186 m_LocalDestination (localDestination), 187 m_OutboundTunnel (nullptr), m_InboundTunnel (nullptr), 188 m_Nickname(nickname), m_InHost(inhost), m_OutHost(outhost), 189 m_InPort(inport), m_OutPort(outport), m_Quiet(quiet), m_IsRunning(false) 190 { 191 } 192 193 BOBDestination::~BOBDestination () 194 { 195 delete m_OutboundTunnel; 196 delete m_InboundTunnel; 197 i2p::client::context.DeleteLocalDestination (m_LocalDestination); 198 } 199 200 void BOBDestination::Start () 201 { 202 if (m_OutboundTunnel) m_OutboundTunnel->Start (); 203 if (m_InboundTunnel) m_InboundTunnel->Start (); 204 m_IsRunning = true; 205 } 206 207 void BOBDestination::Stop () 208 { 209 StopTunnels (); 210 m_LocalDestination->Stop (); 211 } 212 213 void BOBDestination::StopTunnels () 214 { 215 m_IsRunning = false; 216 if (m_OutboundTunnel) 217 { 218 m_OutboundTunnel->Stop (); 219 delete m_OutboundTunnel; 220 m_OutboundTunnel = nullptr; 221 } 222 if (m_InboundTunnel) 223 { 224 m_InboundTunnel->Stop (); 225 delete m_InboundTunnel; 226 m_InboundTunnel = nullptr; 227 } 228 } 229 230 void BOBDestination::CreateInboundTunnel (uint16_t port, const std::string& inhost) 231 { 232 if (!m_InboundTunnel) 233 { 234 // update inport and inhost (user can stop tunnel and change) 235 m_InPort = port; 236 m_InHost = inhost; 237 boost::asio::ip::tcp::endpoint ep(boost::asio::ip::tcp::v4(), port); 238 if (!inhost.empty ()) 239 { 240 boost::system::error_code ec; 241 auto addr = boost::asio::ip::make_address (inhost, ec); 242 if (!ec) 243 ep.address (addr); 244 else 245 LogPrint (eLogError, "BOB: ", ec.message ()); 246 } 247 m_InboundTunnel = new BOBI2PInboundTunnel (ep, m_LocalDestination); 248 } 249 } 250 251 void BOBDestination::CreateOutboundTunnel (const std::string& outhost, uint16_t port, bool quiet) 252 { 253 if (!m_OutboundTunnel) 254 { 255 // update outport and outhost (user can stop tunnel and change) 256 m_OutPort = port; 257 m_OutHost = outhost; 258 m_OutboundTunnel = new BOBI2POutboundTunnel (outhost, port, m_LocalDestination, quiet); 259 } 260 } 261 262 BOBCommandSession::BOBCommandSession (BOBCommandChannel& owner): 263 m_Owner (owner), m_Socket (m_Owner.GetService ()), 264 m_ReceiveBuffer(BOB_COMMAND_BUFFER_SIZE + 1), m_SendBuffer(BOB_COMMAND_BUFFER_SIZE + 1), 265 m_IsOpen (true), m_IsQuiet (false), m_IsActive (false), 266 m_InPort (0), m_OutPort (0), m_CurrentDestination (nullptr) 267 { 268 } 269 270 BOBCommandSession::~BOBCommandSession () 271 { 272 } 273 274 void BOBCommandSession::Terminate () 275 { 276 m_Socket.close (); 277 m_IsOpen = false; 278 } 279 280 void BOBCommandSession::Receive () 281 { 282 boost::asio::async_read_until(m_Socket, m_ReceiveBuffer, '\n', 283 std::bind(&BOBCommandSession::HandleReceivedLine, shared_from_this(), 284 std::placeholders::_1, std::placeholders::_2)); 285 } 286 287 void BOBCommandSession::HandleReceivedLine(const boost::system::error_code& ecode, std::size_t bytes_transferred) 288 { 289 if(ecode) 290 { 291 LogPrint (eLogError, "BOB: Command channel read error: ", ecode.message()); 292 if (ecode != boost::asio::error::operation_aborted) 293 Terminate (); 294 } 295 else 296 { 297 std::string line; 298 299 std::istream is(&m_ReceiveBuffer); 300 std::getline(is, line); 301 302 std::string command, operand; 303 std::istringstream iss(line); 304 iss >> command >> operand; 305 306 // process command 307 auto& handlers = m_Owner.GetCommandHandlers(); 308 auto it = handlers.find(command); 309 if(it != handlers.end()) 310 { 311 (this->*(it->second))(operand.c_str(), operand.length()); 312 } 313 else 314 { 315 LogPrint (eLogError, "BOB: Unknown command ", command.c_str()); 316 SendReplyError ("unknown command"); 317 } 318 } 319 } 320 321 void BOBCommandSession::Send () 322 { 323 boost::asio::async_write (m_Socket, m_SendBuffer, 324 boost::asio::transfer_all (), 325 std::bind(&BOBCommandSession::HandleSent, shared_from_this (), 326 std::placeholders::_1, std::placeholders::_2)); 327 } 328 329 void BOBCommandSession::HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred) 330 { 331 if (ecode) 332 { 333 LogPrint (eLogError, "BOB: Command channel send error: ", ecode.message ()); 334 if (ecode != boost::asio::error::operation_aborted) 335 Terminate (); 336 } 337 else 338 { 339 if (m_IsOpen) 340 Receive (); 341 else 342 Terminate (); 343 } 344 } 345 346 void BOBCommandSession::SendReplyOK (std::string_view msg) 347 { 348 std::ostream os(&m_SendBuffer); 349 os << "OK"; 350 if (!msg.empty ()) 351 os << " " << msg; 352 os << std::endl; 353 Send (); 354 } 355 356 void BOBCommandSession::SendReplyOK (const std::vector<std::string_view>& strings) 357 { 358 std::ostream os(&m_SendBuffer); 359 os << "OK"; 360 if (!strings.empty ()) os << " "; 361 for (auto& it: strings) 362 os << it; 363 os << std::endl; 364 Send (); 365 } 366 367 void BOBCommandSession::SendReplyError (std::string_view msg) 368 { 369 std::ostream os(&m_SendBuffer); 370 os << "ERROR " << msg << std::endl; 371 Send (); 372 } 373 374 void BOBCommandSession::SendVersion () 375 { 376 std::ostream os(&m_SendBuffer); 377 os << "BOB 00.00.10" << std::endl; 378 SendReplyOK(std::string_view()); // empty string 379 } 380 381 void BOBCommandSession::SendRaw (std::string_view data) 382 { 383 std::ostream os(&m_SendBuffer); 384 os << data << std::endl; 385 } 386 387 void BOBCommandSession::BuildStatusLine(bool currentTunnel, std::shared_ptr<BOBDestination> dest, std::string &out) 388 { 389 // helper lambdas 390 const auto issetStr = [](const std::string &str) { return str.empty() ? "not_set" : str; }; // for inhost, outhost 391 const auto issetNum = [&issetStr](const int p) { return issetStr(p == 0 ? "" : std::to_string(p)); }; // for inport, outport 392 const auto destExists = [](const BOBDestination * const dest) { return dest != nullptr; }; 393 const auto destReady = [](const BOBDestination * const dest) { return dest && dest->IsRunning(); }; 394 const auto bool_str = [](const bool v) { return v ? "true" : "false"; }; // bool -> str 395 const auto getProxyType = [](const i2p::client::I2PService* proxy) -> std::string { 396 if (!proxy) return "NONE"; 397 if (dynamic_cast<const i2p::proxy::SOCKSProxy*>(proxy)) return "SOCKS"; 398 if (dynamic_cast<const i2p::proxy::HTTPProxy*>(proxy)) return "HTTPPROXY"; 399 return "UNKNOWN"; 400 }; 401 const auto isProxyRunning = [](const i2p::client::I2PService* proxy) -> bool { 402 return proxy != nullptr; 403 }; 404 405 // tunnel info 406 const std::string nickname = currentTunnel ? m_Nickname : dest->GetNickname(); 407 const bool quiet = currentTunnel ? m_IsQuiet : dest->GetQuiet(); 408 const std::string inhost = issetStr(currentTunnel ? m_InHost : dest->GetInHost()); 409 const std::string outhost = issetStr(currentTunnel ? m_OutHost : dest->GetOutHost()); 410 const std::string inport = issetNum(currentTunnel ? m_InPort : dest->GetInPort()); 411 const std::string outport = issetNum(currentTunnel ? m_OutPort : dest->GetOutPort()); 412 const bool keys = destExists(dest.get ()); // key must exist when destination is created 413 const bool starting = destExists(dest.get ()) && !destReady(dest.get ()); 414 const bool running = destExists(dest.get ()) && destReady(dest.get ()); 415 const bool stopping = false; 416 417 const i2p::client::I2PService* proxy = m_Owner.GetProxy(nickname); 418 const std::string proxyType = getProxyType(proxy); 419 const bool proxyStatus = isProxyRunning(proxy); 420 421 // build line 422 std::stringstream ss; 423 ss << "DATA " 424 << "NICKNAME: " << nickname << " " << "STARTING: " << bool_str(starting) << " " 425 << "RUNNING: " << bool_str(running) << " " << "STOPPING: " << bool_str(stopping) << " " 426 << "KEYS: " << bool_str(keys) << " " << "QUIET: " << bool_str(quiet) << " " 427 << "INPORT: " << inport << " " << "INHOST: " << inhost << " " 428 << "OUTPORT: " << outport << " " << "OUTHOST: " << outhost << " " 429 << "PROXYTYPE: "<< proxyType << " " << "PROXYSTART: " << bool_str(proxyStatus); 430 out = ss.str(); 431 } 432 433 void BOBCommandSession::ZapCommandHandler (const char * operand, size_t len) 434 { 435 LogPrint (eLogDebug, "BOB: zap"); 436 Terminate (); 437 } 438 439 void BOBCommandSession::QuitCommandHandler (const char * operand, size_t len) 440 { 441 LogPrint (eLogDebug, "BOB: quit"); 442 m_IsOpen = false; 443 SendReplyOK ("Bye!"); 444 } 445 446 void BOBCommandSession::StartCommandHandler (const char * operand, size_t len) 447 { 448 LogPrint (eLogDebug, "BOB: start ", m_Nickname); 449 if (m_IsActive) 450 { 451 SendReplyError ("tunnel is active"); 452 return; 453 } 454 if (!m_Keys.GetPublic ()) // keys are set ? 455 { 456 SendReplyError("Keys must be set."); 457 return; 458 } 459 if (m_InPort == 0 460 && m_OutHost.empty() && m_OutPort == 0) 461 { 462 SendReplyError("(inhost):inport or outhost:outport must be set."); 463 return; 464 } 465 if(!m_InHost.empty()) 466 { 467 // TODO: FIXME: temporary validation, until hostname support is added 468 boost::system::error_code ec; 469 boost::asio::ip::make_address(m_InHost, ec); 470 if (ec) 471 { 472 SendReplyError("inhost must be a valid IPv4 address."); 473 return; 474 } 475 } 476 if(!m_OutHost.empty()) 477 { 478 // TODO: FIXME: temporary validation, until hostname support is added 479 boost::system::error_code ec; 480 boost::asio::ip::make_address(m_OutHost, ec); 481 if (ec) 482 { 483 SendReplyError("outhost must be a IPv4 address."); 484 return; 485 } 486 } 487 488 if (!m_CurrentDestination) 489 { 490 m_CurrentDestination = std::make_shared<BOBDestination> (i2p::client::context.CreateNewLocalDestination (m_Keys, true, &m_Options), // deleted in clear command 491 m_Nickname, m_InHost, m_OutHost, m_InPort, m_OutPort, m_IsQuiet); 492 m_Owner.AddDestination (m_Nickname, m_CurrentDestination); 493 } 494 if (!m_tunnelType.has_value()) 495 { 496 if (m_InPort) 497 m_CurrentDestination->CreateInboundTunnel (m_InPort, m_InHost); 498 if (m_OutPort && !m_OutHost.empty ()) 499 m_CurrentDestination->CreateOutboundTunnel (m_OutHost, m_OutPort, m_IsQuiet); 500 m_CurrentDestination->Start (); 501 } 502 else 503 { 504 switch (*m_tunnelType) 505 { 506 case TunnelType::SOCKS: 507 try 508 { 509 auto SocksProxy = std::make_unique<i2p::proxy::SOCKSProxy>(m_Nickname, m_InHost, m_InPort, 510 false, m_OutHost, m_OutPort, m_CurrentDestination->GetLocalDestination()); 511 SocksProxy->Start(); 512 m_Owner.SetProxy(m_Nickname, std::move(SocksProxy)); 513 } 514 catch (std::exception& e) 515 { 516 LogPrint(eLogCritical, "Clients: Exception in SOCKS Proxy: ", e.what()); 517 ThrowFatal ("Unable to start SOCKS Proxy at ", m_InHost, ":", m_InPort, ": ", e.what ()); 518 } 519 break; 520 case TunnelType::HTTP_PROXY: 521 try 522 { 523 auto HttpProxy = std::make_unique<i2p::proxy::HTTPProxy>(m_Nickname, m_InHost, m_InPort, 524 m_OutHost, true, true, m_CurrentDestination->GetLocalDestination()); 525 HttpProxy->Start(); 526 m_Owner.SetProxy(m_Nickname, std::move(HttpProxy)); 527 } 528 catch (std::exception& e) 529 { 530 LogPrint(eLogCritical, "Clients: Exception in HTTP Proxy: ", e.what()); 531 ThrowFatal ("Unable to start HTTP Proxy at ", m_InHost, ":", m_InPort, ": ", e.what ()); 532 } 533 break; 534 default: 535 SendReplyError("Unsupported tunnel type."); 536 return; 537 } 538 } 539 SendReplyOK ("Tunnel starting"); 540 m_IsActive = true; 541 } 542 543 void BOBCommandSession::StopCommandHandler (const char * operand, size_t len) 544 { 545 LogPrint (eLogDebug, "BOB: stop ", m_Nickname); 546 if (!m_IsActive) 547 { 548 SendReplyError ("tunnel is inactive"); 549 return; 550 } 551 auto dest = m_Owner.FindDestination (m_Nickname); 552 auto proxy = m_Owner.GetProxy (m_Nickname); 553 if (dest) 554 { 555 dest->StopTunnels (); 556 SendReplyOK ("Tunnel stopping"); 557 if (proxy) 558 { 559 m_Owner.RemoveProxy (m_Nickname); 560 } 561 } 562 else 563 SendReplyError ("tunnel not found"); 564 m_IsActive = false; 565 } 566 567 void BOBCommandSession::SetNickCommandHandler (const char * operand, size_t len) 568 { 569 LogPrint (eLogDebug, "BOB: setnick ", operand); 570 if(*operand) 571 { 572 auto dest = m_Owner.FindDestination (operand); 573 if (!dest) 574 { 575 m_Nickname = operand; 576 SendReplyOK ({ "Nickname set to ", m_Nickname }); 577 } 578 else 579 SendReplyError ("tunnel is active"); 580 } 581 else 582 SendReplyError ("no nickname has been set"); 583 } 584 585 void BOBCommandSession::GetNickCommandHandler (const char * operand, size_t len) 586 { 587 LogPrint (eLogDebug, "BOB: getnick ", operand); 588 if(*operand) 589 { 590 m_CurrentDestination = m_Owner.FindDestination (operand); 591 auto proxy = m_Owner.GetProxy (operand); 592 if (m_CurrentDestination) 593 { 594 m_Keys = m_CurrentDestination->GetKeys (); 595 m_IsActive = m_CurrentDestination->IsRunning (); 596 if(proxy) 597 m_IsActive = true; 598 m_Nickname = operand; 599 } 600 if (m_Nickname == operand) 601 SendReplyOK ({"Nickname set to ", m_Nickname}); 602 else 603 SendReplyError ("no nickname has been set"); 604 } 605 else 606 SendReplyError ("no nickname has been set"); 607 } 608 609 void BOBCommandSession::NewkeysCommandHandler (const char * operand, size_t len) 610 { 611 LogPrint (eLogDebug, "BOB: newkeys"); 612 i2p::data::SigningKeyType signatureType = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519; 613 i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL; 614 if (*operand) 615 { 616 try 617 { 618 char * operand1 = (char *)strchr (operand, ' '); 619 if (operand1) 620 { 621 *operand1 = 0; operand1++; 622 cryptoType = std::stoi(operand1); 623 } 624 signatureType = std::stoi(operand); 625 } 626 catch (std::invalid_argument& ex) 627 { 628 LogPrint (eLogWarning, "BOB: Error on newkeys: ", ex.what ()); 629 } 630 } 631 632 633 m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (signatureType, cryptoType, true); 634 SendReplyOK (m_Keys.GetPublic ()->ToBase64 ()); 635 } 636 637 void BOBCommandSession::SetkeysCommandHandler (const char * operand, size_t len) 638 { 639 LogPrint (eLogDebug, "BOB: setkeys ", operand); 640 if (*operand && m_Keys.FromBase64 (operand)) 641 SendReplyOK (m_Keys.GetPublic ()->ToBase64 ()); 642 else 643 SendReplyError ("invalid keys"); 644 } 645 646 void BOBCommandSession::GetkeysCommandHandler (const char * operand, size_t len) 647 { 648 LogPrint (eLogDebug, "BOB: getkeys"); 649 if (m_Keys.GetPublic ()) // keys are set ? 650 SendReplyOK (m_Keys.ToBase64 ()); 651 else 652 SendReplyError ("keys are not set"); 653 } 654 655 void BOBCommandSession::GetdestCommandHandler (const char * operand, size_t len) 656 { 657 LogPrint (eLogDebug, "BOB: getdest"); 658 if (m_Keys.GetPublic ()) // keys are set ? 659 SendReplyOK (m_Keys.GetPublic ()->ToBase64 ()); 660 else 661 SendReplyError ("keys are not set"); 662 } 663 664 void BOBCommandSession::OuthostCommandHandler (const char * operand, size_t len) 665 { 666 LogPrint (eLogDebug, "BOB: outhost ", operand); 667 if (*operand) 668 { 669 m_OutHost = operand; 670 SendReplyOK ("outhost set"); 671 } 672 else 673 SendReplyError ("empty outhost"); 674 } 675 676 void BOBCommandSession::OutportCommandHandler (const char * operand, size_t len) 677 { 678 LogPrint (eLogDebug, "BOB: outport ", operand); 679 if (*operand) 680 { 681 int port = std::stoi(operand); 682 if (port >= 0 && port < 65536) 683 { 684 m_OutPort = port; 685 SendReplyOK ("outbound port set"); 686 } 687 else 688 SendReplyError ("port out of range"); 689 } 690 else 691 SendReplyError ("empty outport"); 692 } 693 694 void BOBCommandSession::InhostCommandHandler (const char * operand, size_t len) 695 { 696 LogPrint (eLogDebug, "BOB: inhost ", operand); 697 if (*operand) 698 { 699 m_InHost = operand; 700 SendReplyOK ("inhost set"); 701 } 702 else 703 SendReplyError ("empty inhost"); 704 } 705 706 void BOBCommandSession::InportCommandHandler (const char * operand, size_t len) 707 { 708 LogPrint (eLogDebug, "BOB: inport ", operand); 709 if (*operand) 710 { 711 int port = std::stoi(operand); 712 if (port >= 0 && port < 65536) 713 { 714 m_InPort = port; 715 SendReplyOK ("inbound port set"); 716 } 717 else 718 SendReplyError ("port out of range"); 719 } 720 else 721 SendReplyError ("empty inport"); 722 } 723 724 void BOBCommandSession::QuietCommandHandler (const char * operand, size_t len) 725 { 726 LogPrint (eLogDebug, "BOB: quiet"); 727 if (m_Nickname.length () > 0) 728 { 729 if (!m_IsActive) 730 { 731 m_IsQuiet = true; 732 SendReplyOK ("Quiet set"); 733 } 734 else 735 SendReplyError ("tunnel is active"); 736 } 737 else 738 SendReplyError ("no nickname has been set"); 739 } 740 741 void BOBCommandSession::LookupCommandHandler (const char * operand, size_t len) 742 { 743 LogPrint (eLogDebug, "BOB: lookup ", operand); 744 if (*operand) 745 { 746 auto addr = context.GetAddressBook ().GetAddress (operand); 747 if (!addr) 748 { 749 SendReplyError ("Address Not found"); 750 return; 751 } 752 auto localDestination = (m_CurrentDestination && m_CurrentDestination->IsRunning ()) ? 753 m_CurrentDestination->GetLocalDestination () : i2p::client::context.GetSharedLocalDestination (); 754 if (!localDestination) 755 { 756 SendReplyError ("No local destination"); 757 return; 758 } 759 if (addr->IsIdentHash ()) 760 { 761 // we might have leaseset already 762 auto leaseSet = localDestination->FindLeaseSet (addr->identHash); 763 if (leaseSet) 764 { 765 SendReplyOK (leaseSet->GetIdentity ()->ToBase64 ()); 766 return; 767 } 768 } 769 // trying to request 770 auto s = shared_from_this (); 771 auto requstCallback = [s](std::shared_ptr<i2p::data::LeaseSet> ls) 772 { 773 if (ls) 774 s->SendReplyOK (ls->GetIdentity ()->ToBase64 ()); 775 else 776 s->SendReplyError ("LeaseSet Not found"); 777 }; 778 if (addr->IsIdentHash ()) 779 localDestination->RequestDestination (addr->identHash, requstCallback); 780 else 781 localDestination->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey, requstCallback); 782 } 783 else 784 SendReplyError ("empty lookup address"); 785 } 786 787 void BOBCommandSession::LookupLocalCommandHandler (const char * operand, size_t len) 788 { 789 LogPrint (eLogDebug, "BOB: lookup local ", operand); 790 if (*operand) 791 { 792 auto addr = context.GetAddressBook ().GetAddress (operand); 793 if (!addr) 794 { 795 SendReplyError ("Address Not found"); 796 return; 797 } 798 auto ls = i2p::data::netdb.FindLeaseSet (addr->identHash); 799 if (ls) 800 SendReplyOK (ls->GetIdentity ()->ToBase64 ()); 801 else 802 SendReplyError ("Local LeaseSet Not found"); 803 } 804 else 805 SendReplyError ("empty lookup address"); 806 } 807 808 void BOBCommandSession::PingCommandHandler (const char * operand, size_t len) 809 { 810 LogPrint (eLogDebug, "BOB: ping ", operand); 811 if (*operand) 812 { 813 auto addr = context.GetAddressBook ().GetAddress (operand); 814 if (!addr) 815 { 816 SendReplyError ("Address Not found"); 817 return; 818 } 819 auto localDestination = (m_CurrentDestination && m_CurrentDestination->IsRunning ()) ? 820 m_CurrentDestination->GetLocalDestination () : i2p::client::context.GetSharedLocalDestination (); 821 if (!localDestination) 822 { 823 SendReplyError ("No local destination"); 824 return; 825 } 826 if (addr->IsIdentHash ()) 827 { 828 // we might have leaseset already 829 auto leaseSet = localDestination->FindLeaseSet (addr->identHash); 830 if (leaseSet) 831 { 832 boost::asio::post (localDestination->GetService (), 833 std::bind (&BOBCommandSession::SendPing, shared_from_this (), leaseSet)); 834 return; 835 } 836 } 837 // request LeaseSet 838 if (addr->IsIdentHash ()) 839 localDestination->RequestDestination (addr->identHash, 840 std::bind (&BOBCommandSession::SendPing, shared_from_this (), std::placeholders::_1)); 841 else 842 localDestination->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey, 843 std::bind (&BOBCommandSession::SendPing, shared_from_this (), std::placeholders::_1)); 844 } 845 else 846 SendReplyError ("empty ping address"); 847 } 848 849 void BOBCommandSession::SendPing (std::shared_ptr<const i2p::data::LeaseSet> ls) 850 { 851 if (!ls) 852 { 853 SendReplyError ("LeaseSet Not found"); 854 return; 855 } 856 auto localDestination = (m_CurrentDestination && m_CurrentDestination->IsRunning ()) ? 857 m_CurrentDestination->GetLocalDestination () : i2p::client::context.GetSharedLocalDestination (); 858 if (!localDestination) 859 { 860 SendReplyError ("No local destination"); 861 return; 862 } 863 auto streamingDestination = localDestination->GetStreamingDestination (); 864 if (!streamingDestination) 865 { 866 SendReplyError ("No streaming destination"); 867 return; 868 } 869 auto timer = std::make_shared<boost::asio::deadline_timer>(localDestination->GetService ()); 870 timer->expires_from_now (boost::posix_time::milliseconds(BOB_PING_TIMEOUT)); 871 timer->async_wait ([streamingDestination, s = shared_from_this ()](const boost::system::error_code& ecode) 872 { 873 if (ecode != boost::asio::error::operation_aborted) 874 { 875 LogPrint (eLogDebug, "BOB: Pong not received after ", BOB_PING_TIMEOUT, " millliseconds"); 876 streamingDestination->ResetPongHandler (); 877 s->SendReplyError ("timeout"); 878 } 879 }); 880 auto ts = i2p::util::GetMillisecondsSinceEpoch (); 881 streamingDestination->SetPongHandler ( 882 [streamingDestination, timer, ts, s = shared_from_this ()](const i2p::data::IdentHash * from) 883 { 884 int t = i2p::util::GetMillisecondsSinceEpoch () - ts; 885 if (t < 0) t = 0; 886 streamingDestination->ResetPongHandler (); 887 timer->cancel (); 888 if (from) 889 s->SendReplyOK ({"pong ", "from ", from->ToBase32(), ".b32.i2p: time=", std::to_string (t), " ms"}); 890 else 891 s->SendReplyOK ({"pong: time=", std::to_string (t), " ms"}); 892 }); 893 streamingDestination->SendPing (ls); 894 } 895 896 void BOBCommandSession::ClearCommandHandler (const char * operand, size_t len) 897 { 898 LogPrint (eLogDebug, "BOB: clear"); 899 m_Owner.DeleteDestination (m_Nickname); 900 m_Nickname = ""; 901 SendReplyOK ("cleared"); 902 } 903 904 void BOBCommandSession::ListCommandHandler (const char * operand, size_t len) 905 { 906 LogPrint (eLogDebug, "BOB: list"); 907 std::string statusLine; 908 bool sentCurrent = false; 909 const auto& destinations = m_Owner.GetDestinations (); 910 for (const auto& it: destinations) 911 { 912 BuildStatusLine(false, it.second, statusLine); 913 SendRaw(statusLine); 914 if(m_Nickname.compare(it.second->GetNickname()) == 0) 915 sentCurrent = true; 916 } 917 if(!sentCurrent && !m_Nickname.empty()) 918 { 919 // add the current tunnel to the list. 920 // this is for the incomplete tunnel which has not been started yet. 921 BuildStatusLine(true, m_CurrentDestination, statusLine); 922 SendRaw(statusLine); 923 } 924 SendReplyOK ("Listing done"); 925 } 926 927 void BOBCommandSession::OptionCommandHandler (const char * operand, size_t len) 928 { 929 LogPrint (eLogDebug, "BOB: option ", operand); 930 const char * value = strchr (operand, '='); 931 if (value) 932 { 933 *(const_cast<char *>(value)) = 0; 934 m_Options.Insert (operand, value + 1); 935 SendReplyOK ({ "option ", operand, " set to ", value + 1 }); 936 *(const_cast<char *>(value)) = '='; 937 } 938 else 939 SendReplyError ("malformed"); 940 } 941 942 void BOBCommandSession::StatusCommandHandler (const char * operand, size_t len) 943 { 944 LogPrint (eLogDebug, "BOB: status ", operand); 945 const std::string name = operand; 946 std::string statusLine; 947 948 // always prefer destination 949 auto dest = m_Owner.FindDestination(name); 950 if(dest) 951 { 952 // tunnel destination exists 953 BuildStatusLine(false, dest, statusLine); 954 SendReplyOK(statusLine); 955 } 956 else 957 { 958 if(m_Nickname == name && !name.empty()) 959 { 960 // tunnel is incomplete / has not been started yet 961 BuildStatusLine(true, nullptr, statusLine); 962 SendReplyOK(statusLine); 963 } 964 else 965 { 966 SendReplyError("no nickname has been set"); 967 } 968 } 969 } 970 void BOBCommandSession::HelpCommandHandler (const char * operand, size_t len) 971 { 972 auto helpStrings = m_Owner.GetHelpStrings(); 973 if(!*operand) 974 { 975 std::stringstream ss; 976 ss << "COMMANDS:"; 977 for (auto const& x : helpStrings) 978 { 979 ss << " " << x.first; 980 } 981 SendReplyOK(ss.str ()); 982 } 983 else 984 { 985 auto it = helpStrings.find(operand); 986 if (it != helpStrings.end ()) 987 { 988 SendReplyOK(it->second); 989 return; 990 } 991 SendReplyError("No such command"); 992 } 993 } 994 995 void BOBCommandSession::SetTunnelTypeCommandHandler (const char * operand, size_t len) 996 { 997 std::string_view sv(operand, len); 998 LogPrint (eLogDebug, "BOB: settunneltype ", operand); 999 if (sv == "socks") 1000 { 1001 m_tunnelType = TunnelType::SOCKS; 1002 SendReplyOK ("tunnel type set to SOCKS"); 1003 } 1004 else if (sv == "httpproxy") 1005 { 1006 m_tunnelType = TunnelType::HTTP_PROXY; 1007 SendReplyOK ("tunnel type set to HTTP proxy"); 1008 } 1009 else 1010 { 1011 m_tunnelType.reset(); 1012 SendReplyError ("no tunnel type has been set"); 1013 } 1014 } 1015 1016 BOBCommandChannel::BOBCommandChannel (const std::string& address, uint16_t port): 1017 RunnableService ("BOB"), 1018 m_Acceptor (GetIOService (), boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address(address), port)) 1019 { 1020 // command -> handler 1021 m_CommandHandlers[BOB_COMMAND_ZAP] = &BOBCommandSession::ZapCommandHandler; 1022 m_CommandHandlers[BOB_COMMAND_QUIT] = &BOBCommandSession::QuitCommandHandler; 1023 m_CommandHandlers[BOB_COMMAND_START] = &BOBCommandSession::StartCommandHandler; 1024 m_CommandHandlers[BOB_COMMAND_STOP] = &BOBCommandSession::StopCommandHandler; 1025 m_CommandHandlers[BOB_COMMAND_SETNICK] = &BOBCommandSession::SetNickCommandHandler; 1026 m_CommandHandlers[BOB_COMMAND_GETNICK] = &BOBCommandSession::GetNickCommandHandler; 1027 m_CommandHandlers[BOB_COMMAND_NEWKEYS] = &BOBCommandSession::NewkeysCommandHandler; 1028 m_CommandHandlers[BOB_COMMAND_GETKEYS] = &BOBCommandSession::GetkeysCommandHandler; 1029 m_CommandHandlers[BOB_COMMAND_SETKEYS] = &BOBCommandSession::SetkeysCommandHandler; 1030 m_CommandHandlers[BOB_COMMAND_GETDEST] = &BOBCommandSession::GetdestCommandHandler; 1031 m_CommandHandlers[BOB_COMMAND_OUTHOST] = &BOBCommandSession::OuthostCommandHandler; 1032 m_CommandHandlers[BOB_COMMAND_OUTPORT] = &BOBCommandSession::OutportCommandHandler; 1033 m_CommandHandlers[BOB_COMMAND_INHOST] = &BOBCommandSession::InhostCommandHandler; 1034 m_CommandHandlers[BOB_COMMAND_INPORT] = &BOBCommandSession::InportCommandHandler; 1035 m_CommandHandlers[BOB_COMMAND_QUIET] = &BOBCommandSession::QuietCommandHandler; 1036 m_CommandHandlers[BOB_COMMAND_LOOKUP] = &BOBCommandSession::LookupCommandHandler; 1037 m_CommandHandlers[BOB_COMMAND_LOOKUP_LOCAL] = &BOBCommandSession::LookupLocalCommandHandler; 1038 m_CommandHandlers[BOB_COMMAND_PING] = &BOBCommandSession::PingCommandHandler; 1039 m_CommandHandlers[BOB_COMMAND_CLEAR] = &BOBCommandSession::ClearCommandHandler; 1040 m_CommandHandlers[BOB_COMMAND_LIST] = &BOBCommandSession::ListCommandHandler; 1041 m_CommandHandlers[BOB_COMMAND_OPTION] = &BOBCommandSession::OptionCommandHandler; 1042 m_CommandHandlers[BOB_COMMAND_STATUS] = &BOBCommandSession::StatusCommandHandler; 1043 m_CommandHandlers[BOB_COMMAND_HELP] = &BOBCommandSession::HelpCommandHandler; 1044 m_CommandHandlers[BOB_COMMAND_SETTUNNELTYPE] = &BOBCommandSession::SetTunnelTypeCommandHandler; 1045 // command -> help string 1046 m_HelpStrings[BOB_COMMAND_ZAP] = BOB_HELP_ZAP; 1047 m_HelpStrings[BOB_COMMAND_QUIT] = BOB_HELP_QUIT; 1048 m_HelpStrings[BOB_COMMAND_START] = BOB_HELP_START; 1049 m_HelpStrings[BOB_COMMAND_STOP] = BOB_HELP_STOP; 1050 m_HelpStrings[BOB_COMMAND_SETNICK] = BOB_HELP_SETNICK; 1051 m_HelpStrings[BOB_COMMAND_GETNICK] = BOB_HELP_GETNICK; 1052 m_HelpStrings[BOB_COMMAND_NEWKEYS] = BOB_HELP_NEWKEYS; 1053 m_HelpStrings[BOB_COMMAND_GETKEYS] = BOB_HELP_GETKEYS; 1054 m_HelpStrings[BOB_COMMAND_SETKEYS] = BOB_HELP_SETKEYS; 1055 m_HelpStrings[BOB_COMMAND_GETDEST] = BOB_HELP_GETDEST; 1056 m_HelpStrings[BOB_COMMAND_OUTHOST] = BOB_HELP_OUTHOST; 1057 m_HelpStrings[BOB_COMMAND_OUTPORT] = BOB_HELP_OUTPORT; 1058 m_HelpStrings[BOB_COMMAND_INHOST] = BOB_HELP_INHOST; 1059 m_HelpStrings[BOB_COMMAND_INPORT] = BOB_HELP_INPORT; 1060 m_HelpStrings[BOB_COMMAND_QUIET] = BOB_HELP_QUIET; 1061 m_HelpStrings[BOB_COMMAND_LOOKUP] = BOB_HELP_LOOKUP; 1062 m_HelpStrings[BOB_COMMAND_LOOKUP_LOCAL] = BOB_HELP_LOOKUP_LOCAL; 1063 m_HelpStrings[BOB_COMMAND_CLEAR] = BOB_HELP_CLEAR; 1064 m_HelpStrings[BOB_COMMAND_LIST] = BOB_HELP_LIST; 1065 m_HelpStrings[BOB_COMMAND_OPTION] = BOB_HELP_OPTION; 1066 m_HelpStrings[BOB_COMMAND_STATUS] = BOB_HELP_STATUS; 1067 m_HelpStrings[BOB_COMMAND_SETTUNNELTYPE] = BOB_HELP_SETTUNNELTYPE; 1068 m_HelpStrings[BOB_COMMAND_PING] = BOB_HELP_PING; 1069 m_HelpStrings[BOB_COMMAND_HELP] = BOB_HELP_HELP; 1070 } 1071 1072 BOBCommandChannel::~BOBCommandChannel () 1073 { 1074 if (IsRunning ()) 1075 Stop (); 1076 } 1077 1078 void BOBCommandChannel::Start () 1079 { 1080 Accept (); 1081 StartIOService (); 1082 } 1083 1084 void BOBCommandChannel::Stop () 1085 { 1086 for (auto& it: m_Destinations) 1087 it.second->Stop (); 1088 m_Acceptor.cancel (); 1089 StopIOService (); 1090 } 1091 1092 void BOBCommandChannel::AddDestination (const std::string& name, std::shared_ptr<BOBDestination> dest) 1093 { 1094 m_Destinations.emplace (name, dest); 1095 } 1096 1097 void BOBCommandChannel::DeleteDestination (const std::string& name) 1098 { 1099 auto it = m_Destinations.find (name); 1100 if (it != m_Destinations.end ()) 1101 { 1102 it->second->Stop (); 1103 m_Destinations.erase (it); 1104 } 1105 } 1106 1107 std::shared_ptr<BOBDestination> BOBCommandChannel::FindDestination (const std::string& name) 1108 { 1109 auto it = m_Destinations.find (name); 1110 if (it != m_Destinations.end ()) 1111 return it->second; 1112 return nullptr; 1113 } 1114 1115 void BOBCommandChannel::SetProxy (const std::string& name, std::unique_ptr<I2PService> proxy) 1116 { 1117 m_proxy[name] = std::move(proxy); 1118 } 1119 1120 const I2PService* BOBCommandChannel::GetProxy(const std::string& name) const 1121 { 1122 auto it = m_proxy.find(name); 1123 if (it != m_proxy.end() && it->second) 1124 return it->second.get(); 1125 return nullptr; 1126 } 1127 1128 void BOBCommandChannel::RemoveProxy(const std::string& name) 1129 { 1130 auto it = m_proxy.find (name); 1131 if (it != m_proxy.end ()) 1132 { 1133 m_proxy.erase (it); 1134 } 1135 } 1136 1137 void BOBCommandChannel::Accept () 1138 { 1139 auto newSession = std::make_shared<BOBCommandSession> (*this); 1140 m_Acceptor.async_accept (newSession->GetSocket (), std::bind (&BOBCommandChannel::HandleAccept, this, 1141 std::placeholders::_1, newSession)); 1142 } 1143 1144 void BOBCommandChannel::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<BOBCommandSession> session) 1145 { 1146 if (ecode != boost::asio::error::operation_aborted) 1147 Accept (); 1148 1149 if (!ecode) 1150 { 1151 LogPrint (eLogInfo, "BOB: New command connection from ", session->GetSocket ().remote_endpoint ()); 1152 session->SendVersion (); 1153 } 1154 else 1155 LogPrint (eLogError, "BOB: Accept error: ", ecode.message ()); 1156 } 1157 } 1158 }