/Users/eugenesiegel/btc/bitcoin/src/i2p.cpp
| Line | Count | Source (jump to first uncovered line) | 
| 1 |  | // Copyright (c) 2020-present The Bitcoin Core developers | 
| 2 |  | // Distributed under the MIT software license, see the accompanying | 
| 3 |  | // file COPYING or http://www.opensource.org/licenses/mit-license.php. | 
| 4 |  |  | 
| 5 |  | #include <chainparams.h> | 
| 6 |  | #include <common/args.h> | 
| 7 |  | #include <compat/compat.h> | 
| 8 |  | #include <compat/endian.h> | 
| 9 |  | #include <crypto/sha256.h> | 
| 10 |  | #include <i2p.h> | 
| 11 |  | #include <logging.h> | 
| 12 |  | #include <netaddress.h> | 
| 13 |  | #include <netbase.h> | 
| 14 |  | #include <random.h> | 
| 15 |  | #include <script/parsing.h> | 
| 16 |  | #include <sync.h> | 
| 17 |  | #include <tinyformat.h> | 
| 18 |  | #include <util/fs.h> | 
| 19 |  | #include <util/readwritefile.h> | 
| 20 |  | #include <util/sock.h> | 
| 21 |  | #include <util/strencodings.h> | 
| 22 |  | #include <util/threadinterrupt.h> | 
| 23 |  |  | 
| 24 |  | #include <chrono> | 
| 25 |  | #include <memory> | 
| 26 |  | #include <ranges> | 
| 27 |  | #include <stdexcept> | 
| 28 |  | #include <string> | 
| 29 |  |  | 
| 30 |  | using util::Split; | 
| 31 |  |  | 
| 32 |  | namespace i2p { | 
| 33 |  |  | 
| 34 |  | /** | 
| 35 |  |  * Swap Standard Base64 <-> I2P Base64. | 
| 36 |  |  * Standard Base64 uses `+` and `/` as last two characters of its alphabet. | 
| 37 |  |  * I2P Base64 uses `-` and `~` respectively. | 
| 38 |  |  * So it is easy to detect in which one is the input and convert to the other. | 
| 39 |  |  * @param[in] from Input to convert. | 
| 40 |  |  * @return converted `from` | 
| 41 |  |  */ | 
| 42 |  | static std::string SwapBase64(const std::string& from) | 
| 43 | 0 | { | 
| 44 | 0 |     std::string to; | 
| 45 | 0 |     to.resize(from.size()); | 
| 46 | 0 |     for (size_t i = 0; i < from.size(); ++i) { | 
| 47 | 0 |         switch (from[i]) { | 
| 48 | 0 |         case '-': | 
| 49 | 0 |             to[i] = '+'; | 
| 50 | 0 |             break; | 
| 51 | 0 |         case '~': | 
| 52 | 0 |             to[i] = '/'; | 
| 53 | 0 |             break; | 
| 54 | 0 |         case '+': | 
| 55 | 0 |             to[i] = '-'; | 
| 56 | 0 |             break; | 
| 57 | 0 |         case '/': | 
| 58 | 0 |             to[i] = '~'; | 
| 59 | 0 |             break; | 
| 60 | 0 |         default: | 
| 61 | 0 |             to[i] = from[i]; | 
| 62 | 0 |             break; | 
| 63 | 0 |         } | 
| 64 | 0 |     } | 
| 65 | 0 |     return to; | 
| 66 | 0 | } | 
| 67 |  |  | 
| 68 |  | /** | 
| 69 |  |  * Decode an I2P-style Base64 string. | 
| 70 |  |  * @param[in] i2p_b64 I2P-style Base64 string. | 
| 71 |  |  * @return decoded `i2p_b64` | 
| 72 |  |  * @throw std::runtime_error if decoding fails | 
| 73 |  |  */ | 
| 74 |  | static Binary DecodeI2PBase64(const std::string& i2p_b64) | 
| 75 | 0 | { | 
| 76 | 0 |     const std::string& std_b64 = SwapBase64(i2p_b64); | 
| 77 | 0 |     auto decoded = DecodeBase64(std_b64); | 
| 78 | 0 |     if (!decoded) { | 
| 79 | 0 |         throw std::runtime_error(strprintf("Cannot decode Base64: \"%s\"", i2p_b64));| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 80 | 0 |     } | 
| 81 | 0 |     return std::move(*decoded); | 
| 82 | 0 | } | 
| 83 |  |  | 
| 84 |  | /** | 
| 85 |  |  * Derive the .b32.i2p address of an I2P destination (binary). | 
| 86 |  |  * @param[in] dest I2P destination. | 
| 87 |  |  * @return the address that corresponds to `dest` | 
| 88 |  |  * @throw std::runtime_error if conversion fails | 
| 89 |  |  */ | 
| 90 |  | static CNetAddr DestBinToAddr(const Binary& dest) | 
| 91 | 0 | { | 
| 92 | 0 |     CSHA256 hasher; | 
| 93 | 0 |     hasher.Write(dest.data(), dest.size()); | 
| 94 | 0 |     unsigned char hash[CSHA256::OUTPUT_SIZE]; | 
| 95 | 0 |     hasher.Finalize(hash); | 
| 96 |  | 
 | 
| 97 | 0 |     CNetAddr addr; | 
| 98 | 0 |     const std::string addr_str = EncodeBase32(hash, false) + ".b32.i2p"; | 
| 99 | 0 |     if (!addr.SetSpecial(addr_str)) { | 
| 100 | 0 |         throw std::runtime_error(strprintf("Cannot parse I2P address: \"%s\"", addr_str));| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 101 | 0 |     } | 
| 102 |  |  | 
| 103 | 0 |     return addr; | 
| 104 | 0 | } | 
| 105 |  |  | 
| 106 |  | /** | 
| 107 |  |  * Derive the .b32.i2p address of an I2P destination (I2P-style Base64). | 
| 108 |  |  * @param[in] dest I2P destination. | 
| 109 |  |  * @return the address that corresponds to `dest` | 
| 110 |  |  * @throw std::runtime_error if conversion fails | 
| 111 |  |  */ | 
| 112 |  | static CNetAddr DestB64ToAddr(const std::string& dest) | 
| 113 | 0 | { | 
| 114 | 0 |     const Binary& decoded = DecodeI2PBase64(dest); | 
| 115 | 0 |     return DestBinToAddr(decoded); | 
| 116 | 0 | } | 
| 117 |  |  | 
| 118 |  | namespace sam { | 
| 119 |  |  | 
| 120 |  | Session::Session(const fs::path& private_key_file, | 
| 121 |  |                  const Proxy& control_host, | 
| 122 |  |                  std::shared_ptr<CThreadInterrupt> interrupt) | 
| 123 | 0 |     : m_private_key_file{private_key_file}, | 
| 124 | 0 |       m_control_host{control_host}, | 
| 125 | 0 |       m_interrupt{interrupt}, | 
| 126 | 0 |       m_transient{false} | 
| 127 | 0 | { | 
| 128 | 0 | } | 
| 129 |  |  | 
| 130 |  | Session::Session(const Proxy& control_host, std::shared_ptr<CThreadInterrupt> interrupt) | 
| 131 | 0 |     : m_control_host{control_host}, | 
| 132 | 0 |       m_interrupt{interrupt}, | 
| 133 | 0 |       m_transient{true} | 
| 134 | 0 | { | 
| 135 | 0 | } | 
| 136 |  |  | 
| 137 |  | Session::~Session() | 
| 138 | 0 | { | 
| 139 | 0 |     LOCK(m_mutex); | Line | Count | Source |  | 259 | 0 | #define LOCK(cs) UniqueLock UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__) | Line | Count | Source |  | 11 | 0 | #define UNIQUE_NAME(name) PASTE2(name, __COUNTER__) | Line | Count | Source |  | 9 | 0 | #define PASTE2(x, y) PASTE(x, y) | Line | Count | Source |  | 8 | 0 | #define PASTE(x, y) x ## y | 
 | 
 | 
 | 
 | 
| 140 | 0 |     Disconnect(); | 
| 141 | 0 | } | 
| 142 |  |  | 
| 143 |  | bool Session::Listen(Connection& conn) | 
| 144 | 0 | { | 
| 145 | 0 |     try { | 
| 146 | 0 |         LOCK(m_mutex); | Line | Count | Source |  | 259 | 0 | #define LOCK(cs) UniqueLock UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__) | Line | Count | Source |  | 11 | 0 | #define UNIQUE_NAME(name) PASTE2(name, __COUNTER__) | Line | Count | Source |  | 9 | 0 | #define PASTE2(x, y) PASTE(x, y) | Line | Count | Source |  | 8 | 0 | #define PASTE(x, y) x ## y | 
 | 
 | 
 | 
 | 
| 147 | 0 |         CreateIfNotCreatedAlready(); | 
| 148 | 0 |         conn.me = m_my_addr; | 
| 149 | 0 |         conn.sock = StreamAccept(); | 
| 150 | 0 |         return true; | 
| 151 | 0 |     } catch (const std::runtime_error& e) { | 
| 152 | 0 |         LogPrintLevel(BCLog::I2P, BCLog::Level::Error, "Couldn't listen: %s\n", e.what()); | Line | Count | Source |  | 373 | 0 |     do {                                                              \ |  | 374 | 0 |         if (LogAcceptCategory((category), (level))) {                 \ |  | 375 | 0 |             bool rate_limit{level >= BCLog::Level::Info};             \ |  | 376 | 0 |             LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \ | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 |  | 377 | 0 |         }                                                             \ |  | 378 | 0 |     } while (0) | 
 | 
| 153 | 0 |         CheckControlSock(); | 
| 154 | 0 |     } | 
| 155 | 0 |     return false; | 
| 156 | 0 | } | 
| 157 |  |  | 
| 158 |  | bool Session::Accept(Connection& conn) | 
| 159 | 0 | { | 
| 160 | 0 |     AssertLockNotHeld(m_mutex); | Line | Count | Source |  | 142 | 0 | #define AssertLockNotHeld(cs) AssertLockNotHeldInline(#cs, __FILE__, __LINE__, &cs) | 
 | 
| 161 |  | 
 | 
| 162 | 0 |     std::string errmsg; | 
| 163 | 0 |     bool disconnect{false}; | 
| 164 |  | 
 | 
| 165 | 0 |     while (!m_interrupt->interrupted()) { | 
| 166 | 0 |         Sock::Event occurred; | 
| 167 | 0 |         if (!conn.sock->Wait(MAX_WAIT_FOR_IO, Sock::RECV, &occurred)) { | 
| 168 | 0 |             errmsg = "wait on socket failed"; | 
| 169 | 0 |             break; | 
| 170 | 0 |         } | 
| 171 |  |  | 
| 172 | 0 |         if (occurred == 0) { | 
| 173 |  |             // Timeout, no incoming connections or errors within MAX_WAIT_FOR_IO. | 
| 174 | 0 |             continue; | 
| 175 | 0 |         } | 
| 176 |  |  | 
| 177 | 0 |         std::string peer_dest; | 
| 178 | 0 |         try { | 
| 179 | 0 |             peer_dest = conn.sock->RecvUntilTerminator('\n', MAX_WAIT_FOR_IO, *m_interrupt, MAX_MSG_SIZE); | 
| 180 | 0 |         } catch (const std::runtime_error& e) { | 
| 181 | 0 |             errmsg = e.what(); | 
| 182 | 0 |             break; | 
| 183 | 0 |         } | 
| 184 |  |  | 
| 185 | 0 |         CNetAddr peer_addr; | 
| 186 | 0 |         try { | 
| 187 | 0 |             peer_addr = DestB64ToAddr(peer_dest); | 
| 188 | 0 |         } catch (const std::runtime_error& e) { | 
| 189 |  |             // The I2P router is expected to send the Base64 of the connecting peer, | 
| 190 |  |             // but it may happen that something like this is sent instead: | 
| 191 |  |             // STREAM STATUS RESULT=I2P_ERROR MESSAGE="Session was closed" | 
| 192 |  |             // In that case consider the session damaged and close it right away, | 
| 193 |  |             // even if the control socket is alive. | 
| 194 | 0 |             if (peer_dest.find("RESULT=I2P_ERROR") != std::string::npos) { | 
| 195 | 0 |                 errmsg = strprintf("unexpected reply that hints the session is unusable: %s", peer_dest);| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 196 | 0 |                 disconnect = true; | 
| 197 | 0 |             } else { | 
| 198 | 0 |                 errmsg = e.what(); | 
| 199 | 0 |             } | 
| 200 | 0 |             break; | 
| 201 | 0 |         } | 
| 202 |  |  | 
| 203 | 0 |         conn.peer = CService(peer_addr, I2P_SAM31_PORT); | 
| 204 |  | 
 | 
| 205 | 0 |         return true; | 
| 206 | 0 |     } | 
| 207 |  |  | 
| 208 | 0 |     if (m_interrupt->interrupted()) { | 
| 209 | 0 |         LogPrintLevel(BCLog::I2P, BCLog::Level::Debug, "Accept was interrupted\n"); | Line | Count | Source |  | 373 | 0 |     do {                                                              \ |  | 374 | 0 |         if (LogAcceptCategory((category), (level))) {                 \ |  | 375 | 0 |             bool rate_limit{level >= BCLog::Level::Info};             \ |  | 376 | 0 |             LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \ | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 |  | 377 | 0 |         }                                                             \ |  | 378 | 0 |     } while (0) | 
 | 
| 210 | 0 |     } else { | 
| 211 | 0 |         LogPrintLevel(BCLog::I2P, BCLog::Level::Debug, "Error accepting%s: %s\n", disconnect ? " (will close the session)" : "", errmsg); | Line | Count | Source |  | 373 | 0 |     do {                                                              \ |  | 374 | 0 |         if (LogAcceptCategory((category), (level))) {                 \ |  | 375 | 0 |             bool rate_limit{level >= BCLog::Level::Info};             \ |  | 376 | 0 |             LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \ | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 |  | 377 | 0 |         }                                                             \ |  | 378 | 0 |     } while (0) | 
 | 
| 212 | 0 |     } | 
| 213 | 0 |     if (disconnect) { | 
| 214 | 0 |         LOCK(m_mutex); | Line | Count | Source |  | 259 | 0 | #define LOCK(cs) UniqueLock UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__) | Line | Count | Source |  | 11 | 0 | #define UNIQUE_NAME(name) PASTE2(name, __COUNTER__) | Line | Count | Source |  | 9 | 0 | #define PASTE2(x, y) PASTE(x, y) | Line | Count | Source |  | 8 | 0 | #define PASTE(x, y) x ## y | 
 | 
 | 
 | 
 | 
| 215 | 0 |         Disconnect(); | 
| 216 | 0 |     } else { | 
| 217 | 0 |         CheckControlSock(); | 
| 218 | 0 |     } | 
| 219 | 0 |     return false; | 
| 220 | 0 | } | 
| 221 |  |  | 
| 222 |  | bool Session::Connect(const CService& to, Connection& conn, bool& proxy_error) | 
| 223 | 0 | { | 
| 224 |  |     // Refuse connecting to arbitrary ports. We don't specify any destination port to the SAM proxy | 
| 225 |  |     // when connecting (SAM 3.1 does not use ports) and it forces/defaults it to I2P_SAM31_PORT. | 
| 226 | 0 |     if (to.GetPort() != I2P_SAM31_PORT) { | 
| 227 | 0 |         LogPrintLevel(BCLog::I2P, BCLog::Level::Debug, "Error connecting to %s, connection refused due to arbitrary port %s\n", to.ToStringAddrPort(), to.GetPort()); | Line | Count | Source |  | 373 | 0 |     do {                                                              \ |  | 374 | 0 |         if (LogAcceptCategory((category), (level))) {                 \ |  | 375 | 0 |             bool rate_limit{level >= BCLog::Level::Info};             \ |  | 376 | 0 |             LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \ | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 |  | 377 | 0 |         }                                                             \ |  | 378 | 0 |     } while (0) | 
 | 
| 228 | 0 |         proxy_error = false; | 
| 229 | 0 |         return false; | 
| 230 | 0 |     } | 
| 231 |  |  | 
| 232 | 0 |     proxy_error = true; | 
| 233 |  | 
 | 
| 234 | 0 |     std::string session_id; | 
| 235 | 0 |     std::unique_ptr<Sock> sock; | 
| 236 | 0 |     conn.peer = to; | 
| 237 |  | 
 | 
| 238 | 0 |     try { | 
| 239 | 0 |         { | 
| 240 | 0 |             LOCK(m_mutex); | Line | Count | Source |  | 259 | 0 | #define LOCK(cs) UniqueLock UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__) | Line | Count | Source |  | 11 | 0 | #define UNIQUE_NAME(name) PASTE2(name, __COUNTER__) | Line | Count | Source |  | 9 | 0 | #define PASTE2(x, y) PASTE(x, y) | Line | Count | Source |  | 8 | 0 | #define PASTE(x, y) x ## y | 
 | 
 | 
 | 
 | 
| 241 | 0 |             CreateIfNotCreatedAlready(); | 
| 242 | 0 |             session_id = m_session_id; | 
| 243 | 0 |             conn.me = m_my_addr; | 
| 244 | 0 |             sock = Hello(); | 
| 245 | 0 |         } | 
| 246 |  | 
 | 
| 247 | 0 |         const Reply& lookup_reply = | 
| 248 | 0 |             SendRequestAndGetReply(*sock, strprintf("NAMING LOOKUP NAME=%s", to.ToStringAddr()));| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 249 |  | 
 | 
| 250 | 0 |         const std::string& dest = lookup_reply.Get("VALUE"); | 
| 251 |  | 
 | 
| 252 | 0 |         const Reply& connect_reply = SendRequestAndGetReply( | 
| 253 | 0 |             *sock, strprintf("STREAM CONNECT ID=%s DESTINATION=%s SILENT=false", session_id, dest),| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 254 | 0 |             false); | 
| 255 |  | 
 | 
| 256 | 0 |         const std::string& result = connect_reply.Get("RESULT"); | 
| 257 |  | 
 | 
| 258 | 0 |         if (result == "OK") { | 
| 259 | 0 |             conn.sock = std::move(sock); | 
| 260 | 0 |             return true; | 
| 261 | 0 |         } | 
| 262 |  |  | 
| 263 | 0 |         if (result == "INVALID_ID") { | 
| 264 | 0 |             LOCK(m_mutex); | Line | Count | Source |  | 259 | 0 | #define LOCK(cs) UniqueLock UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__) | Line | Count | Source |  | 11 | 0 | #define UNIQUE_NAME(name) PASTE2(name, __COUNTER__) | Line | Count | Source |  | 9 | 0 | #define PASTE2(x, y) PASTE(x, y) | Line | Count | Source |  | 8 | 0 | #define PASTE(x, y) x ## y | 
 | 
 | 
 | 
 | 
| 265 | 0 |             Disconnect(); | 
| 266 | 0 |             throw std::runtime_error("Invalid session id"); | 
| 267 | 0 |         } | 
| 268 |  |  | 
| 269 | 0 |         if (result == "CANT_REACH_PEER" || result == "TIMEOUT") { | 
| 270 | 0 |             proxy_error = false; | 
| 271 | 0 |         } | 
| 272 |  | 
 | 
| 273 | 0 |         throw std::runtime_error(strprintf("\"%s\"", connect_reply.full));| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 274 | 0 |     } catch (const std::runtime_error& e) { | 
| 275 | 0 |         LogPrintLevel(BCLog::I2P, BCLog::Level::Debug, "Error connecting to %s: %s\n", to.ToStringAddrPort(), e.what()); | Line | Count | Source |  | 373 | 0 |     do {                                                              \ |  | 374 | 0 |         if (LogAcceptCategory((category), (level))) {                 \ |  | 375 | 0 |             bool rate_limit{level >= BCLog::Level::Info};             \ |  | 376 | 0 |             LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \ | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 |  | 377 | 0 |         }                                                             \ |  | 378 | 0 |     } while (0) | 
 | 
| 276 | 0 |         CheckControlSock(); | 
| 277 | 0 |         return false; | 
| 278 | 0 |     } | 
| 279 | 0 | } | 
| 280 |  |  | 
| 281 |  | // Private methods | 
| 282 |  |  | 
| 283 |  | std::string Session::Reply::Get(const std::string& key) const | 
| 284 | 0 | { | 
| 285 | 0 |     const auto& pos = keys.find(key); | 
| 286 | 0 |     if (pos == keys.end() || !pos->second.has_value()) { | 
| 287 | 0 |         throw std::runtime_error( | 
| 288 | 0 |             strprintf("Missing %s= in the reply to \"%s\": \"%s\"", key, request, full));| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 289 | 0 |     } | 
| 290 | 0 |     return pos->second.value(); | 
| 291 | 0 | } | 
| 292 |  |  | 
| 293 |  | Session::Reply Session::SendRequestAndGetReply(const Sock& sock, | 
| 294 |  |                                                const std::string& request, | 
| 295 |  |                                                bool check_result_ok) const | 
| 296 | 0 | { | 
| 297 | 0 |     sock.SendComplete(request + "\n", MAX_WAIT_FOR_IO, *m_interrupt); | 
| 298 |  | 
 | 
| 299 | 0 |     Reply reply; | 
| 300 |  |  | 
| 301 |  |     // Don't log the full "SESSION CREATE ..." because it contains our private key. | 
| 302 | 0 |     reply.request = request.starts_with("SESSION CREATE") ? "SESSION CREATE ..." : request; | 
| 303 |  |  | 
| 304 |  |     // It could take a few minutes for the I2P router to reply as it is querying the I2P network | 
| 305 |  |     // (when doing name lookup, for example). Notice: `RecvUntilTerminator()` is checking | 
| 306 |  |     // `m_interrupt` more often, so we would not be stuck here for long if `m_interrupt` is | 
| 307 |  |     // signaled. | 
| 308 | 0 |     static constexpr auto recv_timeout = 3min; | 
| 309 |  | 
 | 
| 310 | 0 |     reply.full = sock.RecvUntilTerminator('\n', recv_timeout, *m_interrupt, MAX_MSG_SIZE); | 
| 311 |  | 
 | 
| 312 | 0 |     for (const auto& kv : Split(reply.full, ' ')) { | 
| 313 | 0 |         const auto pos{std::ranges::find(kv, '=')}; | 
| 314 | 0 |         if (pos != kv.end()) { | 
| 315 | 0 |             reply.keys.emplace(std::string{kv.begin(), pos}, std::string{pos + 1, kv.end()}); | 
| 316 | 0 |         } else { | 
| 317 | 0 |             reply.keys.emplace(std::string{kv.begin(), kv.end()}, std::nullopt); | 
| 318 | 0 |         } | 
| 319 | 0 |     } | 
| 320 |  | 
 | 
| 321 | 0 |     if (check_result_ok && reply.Get("RESULT") != "OK") { | 
| 322 | 0 |         throw std::runtime_error( | 
| 323 | 0 |             strprintf("Unexpected reply to \"%s\": \"%s\"", request, reply.full));| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 324 | 0 |     } | 
| 325 |  |  | 
| 326 | 0 |     return reply; | 
| 327 | 0 | } | 
| 328 |  |  | 
| 329 |  | std::unique_ptr<Sock> Session::Hello() const | 
| 330 | 0 | { | 
| 331 | 0 |     auto sock = m_control_host.Connect(); | 
| 332 |  | 
 | 
| 333 | 0 |     if (!sock) { | 
| 334 | 0 |         throw std::runtime_error(strprintf("Cannot connect to %s", m_control_host.ToString()));| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 335 | 0 |     } | 
| 336 |  |  | 
| 337 | 0 |     SendRequestAndGetReply(*sock, "HELLO VERSION MIN=3.1 MAX=3.1"); | 
| 338 |  | 
 | 
| 339 | 0 |     return sock; | 
| 340 | 0 | } | 
| 341 |  |  | 
| 342 |  | void Session::CheckControlSock() | 
| 343 | 0 | { | 
| 344 | 0 |     LOCK(m_mutex); | Line | Count | Source |  | 259 | 0 | #define LOCK(cs) UniqueLock UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__) | Line | Count | Source |  | 11 | 0 | #define UNIQUE_NAME(name) PASTE2(name, __COUNTER__) | Line | Count | Source |  | 9 | 0 | #define PASTE2(x, y) PASTE(x, y) | Line | Count | Source |  | 8 | 0 | #define PASTE(x, y) x ## y | 
 | 
 | 
 | 
 | 
| 345 |  | 
 | 
| 346 | 0 |     std::string errmsg; | 
| 347 | 0 |     if (m_control_sock && !m_control_sock->IsConnected(errmsg)) { | 
| 348 | 0 |         LogPrintLevel(BCLog::I2P, BCLog::Level::Debug, "Control socket error: %s\n", errmsg); | Line | Count | Source |  | 373 | 0 |     do {                                                              \ |  | 374 | 0 |         if (LogAcceptCategory((category), (level))) {                 \ |  | 375 | 0 |             bool rate_limit{level >= BCLog::Level::Info};             \ |  | 376 | 0 |             LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \ | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 |  | 377 | 0 |         }                                                             \ |  | 378 | 0 |     } while (0) | 
 | 
| 349 | 0 |         Disconnect(); | 
| 350 | 0 |     } | 
| 351 | 0 | } | 
| 352 |  |  | 
| 353 |  | void Session::DestGenerate(const Sock& sock) | 
| 354 | 0 | { | 
| 355 |  |     // https://geti2p.net/spec/common-structures#key-certificates | 
| 356 |  |     // "7" or "EdDSA_SHA512_Ed25519" - "Recent Router Identities and Destinations". | 
| 357 |  |     // Use "7" because i2pd <2.24.0 does not recognize the textual form. | 
| 358 |  |     // If SIGNATURE_TYPE is not specified, then the default one is DSA_SHA1. | 
| 359 | 0 |     const Reply& reply = SendRequestAndGetReply(sock, "DEST GENERATE SIGNATURE_TYPE=7", false); | 
| 360 |  | 
 | 
| 361 | 0 |     m_private_key = DecodeI2PBase64(reply.Get("PRIV")); | 
| 362 | 0 | } | 
| 363 |  |  | 
| 364 |  | void Session::GenerateAndSavePrivateKey(const Sock& sock) | 
| 365 | 0 | { | 
| 366 | 0 |     DestGenerate(sock); | 
| 367 |  |  | 
| 368 |  |     // umask is set to 0077 in common/system.cpp, which is ok. | 
| 369 | 0 |     if (!WriteBinaryFile(m_private_key_file, | 
| 370 | 0 |                          std::string(m_private_key.begin(), m_private_key.end()))) { | 
| 371 | 0 |         throw std::runtime_error( | 
| 372 | 0 |             strprintf("Cannot save I2P private key to %s", fs::quoted(fs::PathToString(m_private_key_file))));| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 373 | 0 |     } | 
| 374 | 0 | } | 
| 375 |  |  | 
| 376 |  | Binary Session::MyDestination() const | 
| 377 | 0 | { | 
| 378 |  |     // From https://geti2p.net/spec/common-structures#destination: | 
| 379 |  |     // "They are 387 bytes plus the certificate length specified at bytes 385-386, which may be | 
| 380 |  |     // non-zero" | 
| 381 | 0 |     static constexpr size_t DEST_LEN_BASE = 387; | 
| 382 | 0 |     static constexpr size_t CERT_LEN_POS = 385; | 
| 383 |  | 
 | 
| 384 | 0 |     uint16_t cert_len; | 
| 385 |  | 
 | 
| 386 | 0 |     if (m_private_key.size() < CERT_LEN_POS + sizeof(cert_len)) { | 
| 387 | 0 |         throw std::runtime_error(strprintf("The private key is too short (%d < %d)",| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 388 | 0 |                                            m_private_key.size(), | 
| 389 | 0 |                                            CERT_LEN_POS + sizeof(cert_len))); | 
| 390 | 0 |     } | 
| 391 |  |  | 
| 392 | 0 |     memcpy(&cert_len, &m_private_key.at(CERT_LEN_POS), sizeof(cert_len)); | 
| 393 | 0 |     cert_len = be16toh_internal(cert_len); | 
| 394 |  | 
 | 
| 395 | 0 |     const size_t dest_len = DEST_LEN_BASE + cert_len; | 
| 396 |  | 
 | 
| 397 | 0 |     if (dest_len > m_private_key.size()) { | 
| 398 | 0 |         throw std::runtime_error(strprintf("Certificate length (%d) designates that the private key should "| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 399 | 0 |                                            "be %d bytes, but it is only %d bytes", | 
| 400 | 0 |                                            cert_len, | 
| 401 | 0 |                                            dest_len, | 
| 402 | 0 |                                            m_private_key.size())); | 
| 403 | 0 |     } | 
| 404 |  |  | 
| 405 | 0 |     return Binary{m_private_key.begin(), m_private_key.begin() + dest_len}; | 
| 406 | 0 | } | 
| 407 |  |  | 
| 408 |  | void Session::CreateIfNotCreatedAlready() | 
| 409 | 0 | { | 
| 410 | 0 |     std::string errmsg; | 
| 411 | 0 |     if (m_control_sock && m_control_sock->IsConnected(errmsg)) { | 
| 412 | 0 |         return; | 
| 413 | 0 |     } | 
| 414 |  |  | 
| 415 | 0 |     const auto session_type = m_transient ? "transient" : "persistent"; | 
| 416 | 0 |     const auto session_id = GetRandHash().GetHex().substr(0, 10); // full is overkill, too verbose in the logs | 
| 417 |  | 
 | 
| 418 | 0 |     LogPrintLevel(BCLog::I2P, BCLog::Level::Debug, "Creating %s SAM session %s with %s\n", session_type, session_id, m_control_host.ToString()); | Line | Count | Source |  | 373 | 0 |     do {                                                              \ |  | 374 | 0 |         if (LogAcceptCategory((category), (level))) {                 \ |  | 375 | 0 |             bool rate_limit{level >= BCLog::Level::Info};             \ |  | 376 | 0 |             LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \ | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 |  | 377 | 0 |         }                                                             \ |  | 378 | 0 |     } while (0) | 
 | 
| 419 |  | 
 | 
| 420 | 0 |     auto sock = Hello(); | 
| 421 |  | 
 | 
| 422 | 0 |     if (m_transient) { | 
| 423 |  |         // The destination (private key) is generated upon session creation and returned | 
| 424 |  |         // in the reply in DESTINATION=. | 
| 425 | 0 |         const Reply& reply = SendRequestAndGetReply( | 
| 426 | 0 |             *sock, | 
| 427 | 0 |             strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=TRANSIENT SIGNATURE_TYPE=7 "| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 428 | 0 |                       "i2cp.leaseSetEncType=4,0 inbound.quantity=1 outbound.quantity=1", | 
| 429 | 0 |                       session_id)); | 
| 430 |  | 
 | 
| 431 | 0 |         m_private_key = DecodeI2PBase64(reply.Get("DESTINATION")); | 
| 432 | 0 |     } else { | 
| 433 |  |         // Read our persistent destination (private key) from disk or generate | 
| 434 |  |         // one and save it to disk. Then use it when creating the session. | 
| 435 | 0 |         const auto& [read_ok, data] = ReadBinaryFile(m_private_key_file); | 
| 436 | 0 |         if (read_ok) { | 
| 437 | 0 |             m_private_key.assign(data.begin(), data.end()); | 
| 438 | 0 |         } else { | 
| 439 | 0 |             GenerateAndSavePrivateKey(*sock); | 
| 440 | 0 |         } | 
| 441 |  | 
 | 
| 442 | 0 |         const std::string& private_key_b64 = SwapBase64(EncodeBase64(m_private_key)); | 
| 443 |  | 
 | 
| 444 | 0 |         SendRequestAndGetReply(*sock, | 
| 445 | 0 |                                strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=%s "| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 446 | 0 |                                          "i2cp.leaseSetEncType=4,0 inbound.quantity=3 outbound.quantity=3", | 
| 447 | 0 |                                          session_id, | 
| 448 | 0 |                                          private_key_b64)); | 
| 449 | 0 |     } | 
| 450 |  | 
 | 
| 451 | 0 |     m_my_addr = CService(DestBinToAddr(MyDestination()), I2P_SAM31_PORT); | 
| 452 | 0 |     m_session_id = session_id; | 
| 453 | 0 |     m_control_sock = std::move(sock); | 
| 454 |  | 
 | 
| 455 | 0 |     LogPrintLevel(BCLog::I2P, BCLog::Level::Info, "%s SAM session %s created, my address=%s\n", | Line | Count | Source |  | 373 | 0 |     do {                                                              \ |  | 374 | 0 |         if (LogAcceptCategory((category), (level))) {                 \ |  | 375 | 0 |             bool rate_limit{level >= BCLog::Level::Info};             \ |  | 376 | 0 |             LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \ | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 |  | 377 | 0 |         }                                                             \ |  | 378 | 0 |     } while (0) | 
 | 
| 456 | 0 |         Capitalize(session_type), | 
| 457 | 0 |         m_session_id, | 
| 458 | 0 |         m_my_addr.ToStringAddrPort()); | 
| 459 | 0 | } | 
| 460 |  |  | 
| 461 |  | std::unique_ptr<Sock> Session::StreamAccept() | 
| 462 | 0 | { | 
| 463 | 0 |     auto sock = Hello(); | 
| 464 |  | 
 | 
| 465 | 0 |     const Reply& reply = SendRequestAndGetReply( | 
| 466 | 0 |         *sock, strprintf("STREAM ACCEPT ID=%s SILENT=false", m_session_id), false);| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 467 |  | 
 | 
| 468 | 0 |     const std::string& result = reply.Get("RESULT"); | 
| 469 |  | 
 | 
| 470 | 0 |     if (result == "OK") { | 
| 471 | 0 |         return sock; | 
| 472 | 0 |     } | 
| 473 |  |  | 
| 474 | 0 |     if (result == "INVALID_ID") { | 
| 475 |  |         // If our session id is invalid, then force session re-creation on next usage. | 
| 476 | 0 |         Disconnect(); | 
| 477 | 0 |     } | 
| 478 |  | 
 | 
| 479 | 0 |     throw std::runtime_error(strprintf("\"%s\"", reply.full));| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 480 | 0 | } | 
| 481 |  |  | 
| 482 |  | void Session::Disconnect() | 
| 483 | 0 | { | 
| 484 | 0 |     if (m_control_sock) { | 
| 485 | 0 |         if (m_session_id.empty()) { | 
| 486 | 0 |             LogPrintLevel(BCLog::I2P, BCLog::Level::Info, "Destroying incomplete SAM session\n"); | Line | Count | Source |  | 373 | 0 |     do {                                                              \ |  | 374 | 0 |         if (LogAcceptCategory((category), (level))) {                 \ |  | 375 | 0 |             bool rate_limit{level >= BCLog::Level::Info};             \ |  | 376 | 0 |             LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \ | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 |  | 377 | 0 |         }                                                             \ |  | 378 | 0 |     } while (0) | 
 | 
| 487 | 0 |         } else { | 
| 488 | 0 |             LogPrintLevel(BCLog::I2P, BCLog::Level::Info, "Destroying SAM session %s\n", m_session_id); | Line | Count | Source |  | 373 | 0 |     do {                                                              \ |  | 374 | 0 |         if (LogAcceptCategory((category), (level))) {                 \ |  | 375 | 0 |             bool rate_limit{level >= BCLog::Level::Info};             \ |  | 376 | 0 |             LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \ | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 |  | 377 | 0 |         }                                                             \ |  | 378 | 0 |     } while (0) | 
 | 
| 489 | 0 |         } | 
| 490 | 0 |         m_control_sock.reset(); | 
| 491 | 0 |     } | 
| 492 | 0 |     m_session_id.clear(); | 
| 493 | 0 | } | 
| 494 |  | } // namespace sam | 
| 495 |  | } // namespace i2p |