// acetone, 2025 // I hate copyright of any kind. This is a public domain. // Original source: http://git.community.i2p/acetone/i2pdtunnelwizard #include "tunnelconstructor.h" #include "notepad.h" #include "randomstringgenerator.h" #include "codec/base64_i2p.hpp" #include "x25519/x25519cpp.h" #include TunnelConstructor::TunnelConstructor() { lengthVariance_.inbound = 0; lengthVariance_.outbound = 0; } bool TunnelConstructor::setName(std::string name) { auto pos = name.find(' '); while (pos != std::string::npos) { name.erase(pos, 1); pos = name.find(' '); } if (name.length() > 30) { name.erase(30); } if (name.empty() or name.find('[') != std::string::npos or name.find(']') != std::string::npos) { errorString_ = Notepad::SetterError::name(lang_); return false; } name_ = name; return true; } bool TunnelConstructor::setTunnelType(TunnelType type) { tunnelType_ = type; return true; } bool TunnelConstructor::setLength(int8_t inbound, int8_t outbound) { if (inbound < 0 or inbound > 8) { errorString_ = Notepad::SetterError::length(lang_); return false; } if (outbound < 0 or outbound > 8) { errorString_ = Notepad::SetterError::length(lang_); return false; } length_.inbound = inbound; length_.outbound = outbound; return true; } bool TunnelConstructor::setLengthVariance(int8_t inbound, int8_t outbound) { if (inbound > 3 or inbound < -3) { errorString_ = Notepad::SetterError::variance(lang_); return false; } if (outbound > 3 or outbound < -3) { errorString_ = Notepad::SetterError::variance(lang_); return false; } lengthVariance_.inbound = inbound; lengthVariance_.outbound = outbound; return true; } bool TunnelConstructor::setQuantity(int8_t inbound, int8_t outbound) { if (inbound > 16 or inbound < 1) { errorString_ = Notepad::SetterError::quantity(lang_); return false; } if (outbound > 16 or outbound < 1) { errorString_ = Notepad::SetterError::quantity(lang_); return false; } quantity_.inbound = inbound; quantity_.outbound = outbound; return true; } bool TunnelConstructor::setBlindedLeaseSet(TunnelConstructor::BlindedType type) { blinded_ = type; return true; } bool TunnelConstructor::setTransient(bool isTransient) { transient_ = isTransient; return true; } bool TunnelConstructor::setKeepAlive(int8_t interval) { if (interval < 0) { interval = 0; } keepAliveInterval_ = interval; return true; } bool TunnelConstructor::setHostOverride(bool wantCustomHost) { hostoverride_ = wantCustomHost; return true; } bool TunnelConstructor::setSsl(const bool isSslUpstream) { ssl_ = isSslUpstream; return true; } bool TunnelConstructor::setMaxStreamCount(int count) { if (count < 1) { errorString_ = Notepad::SetterError::maxStreamCount(lang_); return false; } maxStreamCount_ = count; return true; } bool TunnelConstructor::setProxyOutproxy(bool isEnabled) { proxyOutproxy_ = isEnabled; return true; } bool TunnelConstructor::setComments(bool enabled) { comments_ = enabled; return true; } bool TunnelConstructor::setB33UserCount(uint16_t number) { if (number > 30) { errorString_ = Notepad::SetterError::b33UserCount(lang_); return false; } b33UserCount_ = number; return true; } bool TunnelConstructor::setIrcWebPassword(bool wantToUse) { webircpassword_ = wantToUse; return true; } std::u8string TunnelConstructor::generate() const { std::u8string config; config += u8"[" + std::u8string{ name_.begin(), name_.end() } + u8"]\n"; if (comments_) config += Notepad::ConfigComment::type(lang_, tunnelType_); const auto typeString = tunnelTypeToString(tunnelType_); config += u8"type = " + std::u8string{ typeString.begin(), typeString.end() } + u8"\n"; if (isClientType(tunnelType_)) { if (comments_) config += Notepad::ConfigComment::clientAddress(lang_); config += u8"address = LOCAL_INTERFACE_ADDRESS\n"; if (comments_) config += Notepad::ConfigComment::clientPort(lang_); config += u8"port = LOCAL_INTERFACE_PORT\n"; if (comments_) config += Notepad::ConfigComment::clientDestination(lang_); config += u8"destination = I2P_SERVER_ADDRESS\n"; if (comments_) config += Notepad::ConfigComment::clientDestinationPort(lang_); config += u8"destinationport = I2P_SERVER_PORT\n"; } else { if (comments_) config += Notepad::ConfigComment::serverAddress(lang_); config += u8"address = LOCAL_INTERFACE_ADDRESS\n"; if (comments_) config += Notepad::ConfigComment::serverHost(lang_); config += u8"host = IP_ADDRESS_OF_APPLICATION\n"; if (comments_) config += Notepad::ConfigComment::serverPort(lang_); config += u8"port = APPLICATION_PORT\n"; if (comments_) config += Notepad::ConfigComment::serverInport(lang_); config += u8"inport = I2P_SERVER_PORT\n"; } if (comments_) config += Notepad::ConfigComment::inbound(lang_); if (comments_) config += Notepad::ConfigComment::length(lang_); config += u8"inbound.length = " + std::u8string( reinterpret_cast(std::to_string(length_.inbound).c_str()) ) + u8"\n"; if (comments_) config += Notepad::ConfigComment::quantity(lang_); config += u8"inbound.quantity = " + std::u8string( reinterpret_cast(std::to_string(quantity_.inbound).c_str()) ) + u8"\n"; if (comments_) config += Notepad::ConfigComment::variance(lang_); config += u8"inbound.lengthVariance = " + std::u8string( reinterpret_cast(std::to_string(lengthVariance_.inbound).c_str()) ) + u8"\n"; if (comments_) config += Notepad::ConfigComment::outbound(lang_); if (comments_) config += Notepad::ConfigComment::length(lang_); config += u8"outbound.length = " + std::u8string( reinterpret_cast(std::to_string(length_.outbound).c_str()) ) + u8"\n"; if (comments_) config += Notepad::ConfigComment::quantity(lang_); config += u8"outbound.quantity = " + std::u8string( reinterpret_cast(std::to_string(quantity_.outbound).c_str()) ) + u8"\n"; if (comments_) config += Notepad::ConfigComment::variance(lang_); config += u8"outbound.lengthVariance = " + std::u8string( reinterpret_cast(std::to_string(lengthVariance_.outbound).c_str()) ) + u8"\n"; if (isClientType(tunnelType_)) { if (keepAliveInterval_) { if (comments_) config += Notepad::ConfigComment::clientKeepAlive(lang_); config += u8"keepaliveinterval = " + std::u8string( reinterpret_cast(std::to_string(keepAliveInterval_).c_str()) ) + u8"\n"; } if (blinded_ != BlindedType::none) { if (comments_) config += Notepad::ConfigComment::b33ClientKey(lang_); config += u8"i2cp.leaseSetPrivKey = BASE64_ENCODED_DATA\n"; } if (tunnelType_ == TunnelType::HttpProxy and proxyOutproxy_) { if (comments_) config += Notepad::ConfigComment::httpOutproxy(lang_); config += u8"outproxy = PROXYSERVER\n"; } else if (tunnelType_ == TunnelType::SocksProxy and proxyOutproxy_) { if (comments_) config += Notepad::ConfigComment::socksOutproxy(lang_); config += u8"outproxy = PROXY_ADDRES\n" "outproxyport = PROXY_PORT\n"; } } else // server { if (ssl_) { if (comments_) config += Notepad::ConfigComment::ssl(lang_); config += u8"ssl = true\n"; } if (tunnelType_ != TunnelType::UdpServer) { if (comments_) config += Notepad::ConfigComment::maxStreamCount(lang_); config += u8"i2p.streaming.maxConcurrentStreams = " + std::u8string( reinterpret_cast(std::to_string(maxStreamCount_).c_str()) ) + u8"\n"; } if (tunnelType_ == TunnelType::HttpServer and hostoverride_) { if (comments_) config += Notepad::ConfigComment::hostoverride(lang_); config += u8"hostoverride = HOSTNAME\n"; } else if (tunnelType_ == TunnelType::IrcServer and webircpassword_) { if (comments_) config += Notepad::ConfigComment::ircPassword(lang_); config += u8"webircpassword = PASSWORD_FROM_YOUR_IRC_SERVER_SETTINGS\n"; } if (blinded_ != BlindedType::none and blinded_ != BlindedType::client) // blinded server { if (comments_) config += Notepad::ConfigComment::serverBlinded(lang_); config += u8"signaturetype = 11\n" "i2cp.leaseSetType = 5\n"; if (blinded_ == BlindedType::password) { config += u8"i2cp.leaseSetAuthType = 2\n"; if (comments_) config += Notepad::ConfigComment::serverBlindedWithPsk(lang_); for (uint16_t i = 1; i < b33UserCount_+1; ++i) { // yes. this is C++ casting hell :) // i2cp.leaseSetClient.psk.NUMBER = userNUMBER:b64_password const auto password = RandomStringGenerator::getU8string(32); config += u8"i2cp.leaseSetClient.psk." + std::u8string(reinterpret_cast(std::to_string(i).c_str())) + u8" = user" + std::u8string(reinterpret_cast(std::to_string(i).c_str())) + u8":"; config += std::u8string(reinterpret_cast( cppcodec_samty::base64_i2p::encode( reinterpret_cast(password.c_str()), password.size() ).c_str() )); config += u8"\n"; } } else if (blinded_ == BlindedType::keys) { config += u8"i2cp.leaseSetAuthType = 1\n"; if (comments_) config += Notepad::ConfigComment::serverBlindedWithDh(lang_); for (uint16_t i = 1; i < b33UserCount_+1; ++i) { // i2cp.leaseSetClient.dh.NUMBER = userNUMBER:b64_public_key X25519Keys keys; keys.generateKeys(); config += Notepad::ConfigComment::serverBlindedWithDhPrivateKey(lang_); config += u8"user" + std::u8string(reinterpret_cast(std::to_string(i).c_str())) + u8": "; config += reinterpret_cast( keys.getSecretKeyBase64().c_str() ); config += u8"\n"; config += u8"i2cp.leaseSetClient.dh." + std::u8string(reinterpret_cast(std::to_string(i).c_str())) + u8" = user" + std::u8string(reinterpret_cast(std::to_string(i).c_str())) + u8":"; config += std::u8string(reinterpret_cast( keys.getPublicKeyBase64().c_str() )); config += u8"\n"; } } } } if (transient_) { if (comments_) config += Notepad::ConfigComment::keysTransient(lang_); config += u8"keys = transient-" + RandomStringGenerator::getU8string(5) + u8"\n"; } else { std::string name(name_); std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) { return std::tolower(c); }); if (comments_) config += Notepad::ConfigComment::keys(lang_); config += u8"keys = " + std::u8string( reinterpret_cast(name.c_str()) ) + u8"-" + RandomStringGenerator::getU8string(5) + u8".dat\n"; } if (comments_) config += Notepad::ConfigComment::footer(lang_, not isClientType(tunnelType_) and blinded_ != BlindedType::none, name_); return config; } std::string TunnelConstructor::blindedTypeToString(BlindedType type) { switch(type) { case BlindedType::keys: { return "dh"; } case BlindedType::password: { return "psk"; } case BlindedType::simple: { return "simple"; } case BlindedType::client: { return "client"; } default :{ return "none"; } } } TunnelConstructor::BlindedType TunnelConstructor::stringToBlindedType(const std::string &type) { if (type == "dh") { return BlindedType::keys; } else if (type == "psk") { return BlindedType::password; } else if (type == "simple") { return BlindedType::simple; } else if (type == "client") { return BlindedType::client; } return BlindedType::none; } std::string TunnelConstructor::tunnelTypeToString(TunnelType type) { switch(type) { case TunnelType::TcpClient: { return "client"; } case TunnelType::TcpServer: { return "server"; } case TunnelType::UdpClient: { return "udpclient"; } case TunnelType::UdpServer: { return "udpserver"; } case TunnelType::HttpServer: { return "http"; } case TunnelType::IrcServer: { return "irc"; } case TunnelType::SocksProxy: { return "socks"; } case TunnelType::HttpProxy: { return "httpproxy"; } } return "undefined(bug)"; } TunnelType TunnelConstructor::stringToTunnelType(const std::string &type) { if (type == "client") { return TunnelType::TcpClient; } else if (type == "server") { return TunnelType::TcpServer; } else if (type == "udpclient") { return TunnelType::UdpClient; } else if (type == "udpserver") { return TunnelType::UdpServer; } else if (type == "http") { return TunnelType::HttpServer; } else if (type == "irc") { return TunnelType::IrcServer; } else if (type == "socks") { return TunnelType::SocksProxy; } else if (type == "httpproxy") { return TunnelType::HttpProxy; } return TunnelType::TcpClient; } bool TunnelConstructor::isClientType(TunnelType type) { switch(type) { case TunnelType::TcpClient: { return true; } case TunnelType::UdpClient: { return true; } case TunnelType::HttpProxy: { return true; } case TunnelType::SocksProxy: { return true; } default : { return false; } } }