• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

randombit / botan / 20337284865

18 Dec 2025 12:40PM UTC coverage: 90.392% (+0.005%) from 90.387%
20337284865

push

github

web-flow
Merge pull request #5182 from randombit/jack/fix-boost-warning

Avoid deprecated strand call in Boost asio

101306 of 112074 relevant lines covered (90.39%)

12722957.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.09
/src/cli/tls_proxy.cpp
1
/*
2
* TLS Server Proxy
3
* (C) 2014,2015,2019 Jack Lloyd
4
* (C) 2016 Matthias Gierlings
5
* (C) 2023 René Meusel, Rohde & Schwarz Cybersecurity
6
*
7
* Botan is released under the Simplified BSD License (see license.txt)
8
*/
9

10
#include "cli.h"
11

12
#if defined(BOTAN_HAS_TLS) && defined(BOTAN_HAS_BOOST_ASIO) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
13

14
   #include <atomic>
15
   #include <iostream>
16
   #include <string>
17
   #include <thread>
18
   #include <utility>
19
   #include <vector>
20

21
   #include <boost/asio.hpp>
22
   #include <boost/bind.hpp>
23

24
   #include <botan/hex.h>
25
   #include <botan/pkcs8.h>
26
   #include <botan/rng.h>
27
   #include <botan/tls_server.h>
28
   #include <botan/tls_session_manager_memory.h>
29
   #include <botan/x509cert.h>
30

31
   #if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER)
32
      #include <botan/tls_session_manager_sqlite.h>
33
   #endif
34

35
   #if defined(BOTAN_HAS_OS_UTILS)
36
      #include <botan/internal/os_utils.h>
37
   #endif
38

39
   #include "tls_helpers.h"
40

41
namespace Botan_CLI {
42

43
namespace {
44

45
// NOLINTBEGIN(*-avoid-endl,*-avoid-bind)
46

47
using boost::asio::ip::tcp;
48

49
template <typename T>
50
boost::asio::io_context& get_io_service(T& s) {
4✔
51
   #if BOOST_VERSION >= 107000
52
   //NOLINTNEXTLINE(*-type-static-cast-downcast)
53
   return static_cast<boost::asio::io_context&>((s).get_executor().context());
4✔
54
   #else
55
   return s.get_io_service();
56
   #endif
57
}
58

59
void log_info(const std::string& msg) {
1✔
60
   std::cout << msg << std::endl;
1✔
61
}
1✔
62

63
void log_exception(const char* where, const std::exception& e) {
×
64
   std::cout << where << ' ' << e.what() << std::endl;
×
65
}
×
66

67
void log_error(const char* where, const boost::system::error_code& error) {
8✔
68
   std::cout << where << ' ' << error.message() << std::endl;
16✔
69
}
8✔
70

71
void log_error(const char* msg) {
×
72
   std::cout << msg << std::endl;
×
73
}
×
74

75
void log_binary_message(const char* where, const uint8_t buf[], size_t buf_len) {
76
   BOTAN_UNUSED(where, buf, buf_len);
27✔
77
   //std::cout << where << ' ' << Botan::hex_encode(buf, buf_len) << std::endl;
78
}
79

80
void log_text_message(const char* where, const uint8_t buf[], size_t buf_len) {
81
   BOTAN_UNUSED(where, buf, buf_len);
9✔
82
   //const char* c = reinterpret_cast<const char*>(buf);
83
   //std::cout << where << ' ' << std::string(c, c + buf_len)  << std::endl;
84
}
85

86
class ServerStatus {
87
   public:
88
      explicit ServerStatus(size_t max_clients) : m_max_clients(max_clients), m_clients_serviced(0) {}
1✔
89

90
      bool should_exit() const {
4✔
91
         if(m_max_clients == 0) {
4✔
92
            return false;
93
         }
94

95
         return clients_serviced() >= m_max_clients;
4✔
96
      }
97

98
      void client_serviced() { m_clients_serviced++; }
4✔
99

100
      size_t clients_serviced() const { return m_clients_serviced.load(); }
4✔
101

102
   private:
103
      size_t m_max_clients;
104
      std::atomic<size_t> m_clients_serviced;
105
};
106

107
class tls_proxy_session final : public std::enable_shared_from_this<tls_proxy_session>,
108
                                public Botan::TLS::Callbacks {
109
   public:
110
      static constexpr size_t readbuf_size = 17 * 1024;
111

112
      typedef std::shared_ptr<tls_proxy_session> pointer;
113

114
      static pointer create(boost::asio::io_context& io,
4✔
115
                            const std::shared_ptr<Botan::TLS::Session_Manager>& session_manager,
116
                            const std::shared_ptr<Botan::Credentials_Manager>& credentials,
117
                            const std::shared_ptr<Botan::TLS::Policy>& policy,
118
                            const tcp::resolver::results_type& endpoints) {
119
         auto session = std::make_shared<tls_proxy_session>(io, endpoints);
4✔
120

121
         // Defer the setup of the TLS server to make use of
122
         // shared_from_this() which wouldn't work in the c'tor.
123
         session->setup(session_manager, credentials, policy);
4✔
124

125
         return session;
4✔
126
      }
×
127

128
      tcp::socket& client_socket() { return m_client_socket; }
129

130
      void start() {
4✔
131
         m_c2p.resize(readbuf_size);
4✔
132
         client_read(boost::system::error_code(), 0);  // start read loop
4✔
133
      }
4✔
134

135
      void stop() {
8✔
136
         if(!m_is_closed) {
8✔
137
            /*
138
            Don't need to talk to the server anymore
139
            Client socket is closed during write callback
140
            */
141
            m_server_socket.close();
4✔
142
            m_tls->close();
4✔
143

144
            // Need to explicitly destroy the TLS::Server object to break the
145
            // circular ownership of shared_from_this() and the shared_ptr of
146
            // this kept inside the TLS::Channel.
147
            m_tls.reset();
4✔
148
            m_is_closed = true;
4✔
149
         }
150
      }
8✔
151

152
      tls_proxy_session(boost::asio::io_context& io, tcp::resolver::results_type endpoints) :
4✔
153
            m_strand(io),
4✔
154
            m_server_endpoints(std::move(endpoints)),
4✔
155
            m_client_socket(io),
4✔
156
            m_server_socket(io),
8✔
157
            m_rng(cli_make_rng()) {}
12✔
158

159
   private:
160
      void setup(const std::shared_ptr<Botan::TLS::Session_Manager>& session_manager,
4✔
161
                 const std::shared_ptr<Botan::Credentials_Manager>& credentials,
162
                 const std::shared_ptr<Botan::TLS::Policy>& policy) {
163
         m_tls = std::make_unique<Botan::TLS::Server>(shared_from_this(), session_manager, credentials, policy, m_rng);
4✔
164
      }
4✔
165

166
      void client_read(const boost::system::error_code& error, size_t bytes_transferred) {
19✔
167
         if(error) {
19✔
168
            log_error("Read failed", error);
4✔
169
            stop();
4✔
170
            return;
4✔
171
         }
172

173
         if(m_is_closed) {
15✔
174
            log_error("Received client data after close");
×
175
            return;
×
176
         }
177

178
         try {
15✔
179
            if(!m_tls->is_active()) {
15✔
180
               log_binary_message("From client", m_c2p.data(), bytes_transferred);
181
            }
182
            m_tls->received_data(m_c2p.data(), bytes_transferred);
15✔
183
         } catch(Botan::Exception& e) {
×
184
            log_exception("TLS connection failed", e);
×
185
            stop();
×
186
            return;
×
187
         }
×
188

189
         m_client_socket.async_read_some(
30✔
190
            boost::asio::buffer(m_c2p),
15✔
191
            boost::asio::bind_executor(
30✔
192
               m_strand, [self = shared_from_this()](const boost::system::error_code& ec, std::size_t bytes) {
180✔
193
                  self->client_read(ec, bytes);
15✔
194
               }));
195
      }
196

197
      void handle_client_write_completion(const boost::system::error_code& error) {
22✔
198
         if(error) {
22✔
199
            log_error("Client write", error);
×
200
            stop();
×
201
            return;
×
202
         }
203

204
         m_p2c.clear();
22✔
205

206
         if(m_p2c_pending.empty() && (!m_tls || m_tls->is_closed())) {
22✔
207
            m_client_socket.close();
4✔
208
         }
209
         tls_emit_data({});  // initiate another write if needed
22✔
210
      }
211

212
      void handle_server_write_completion(const boost::system::error_code& error) {
4✔
213
         if(error) {
4✔
214
            log_error("Server write", error);
×
215
            stop();
×
216
            return;
×
217
         }
218

219
         m_p2s.clear();
4✔
220
         proxy_write_to_server({});  // initiate another write if needed
4✔
221
      }
222

223
      void tls_record_received(uint64_t /*rec_no*/, std::span<const uint8_t> buf) override {
4✔
224
         // Immediately bounce message to server
225
         proxy_write_to_server(buf);
4✔
226
      }
4✔
227

228
      void tls_emit_data(std::span<const uint8_t> buf) override {
49✔
229
         if(!buf.empty()) {
49✔
230
            m_p2c_pending.insert(m_p2c_pending.end(), buf.begin(), buf.end());
27✔
231
         }
232

233
         // no write now active and we still have output pending
234
         if(m_p2c.empty() && !m_p2c_pending.empty()) {
49✔
235
            std::swap(m_p2c_pending, m_p2c);
22✔
236

237
            log_binary_message("To Client", m_p2c.data(), m_p2c.size());
22✔
238

239
            boost::asio::async_write(
44✔
240
               m_client_socket,
22✔
241
               boost::asio::buffer(m_p2c),
22✔
242
               boost::asio::bind_executor(
44✔
243
                  m_strand, [self = shared_from_this()](const boost::system::error_code& ec, std::size_t /*bytes*/) {
264✔
244
                     self->handle_client_write_completion(ec);
22✔
245
                  }));
246
         }
247
      }
49✔
248

249
      void proxy_write_to_server(std::span<const uint8_t> buf) {
12✔
250
         if(!buf.empty()) {
12✔
251
            m_p2s_pending.insert(m_p2s_pending.end(), buf.begin(), buf.end());
4✔
252
         }
253

254
         // no write now active and we still have output pending
255
         if(m_p2s.empty() && !m_p2s_pending.empty()) {
12✔
256
            std::swap(m_p2s_pending, m_p2s);
4✔
257

258
            log_text_message("To Server", m_p2s.data(), m_p2s.size());
4✔
259

260
            boost::asio::async_write(
8✔
261
               m_server_socket,
4✔
262
               boost::asio::buffer(m_p2s),
4✔
263
               boost::asio::bind_executor(
8✔
264
                  m_strand, [self = shared_from_this()](const boost::system::error_code& ec, std::size_t /*bytes*/) {
48✔
265
                     self->handle_server_write_completion(ec);
4✔
266
                  }));
267
         }
268
      }
12✔
269

270
      void server_read(const boost::system::error_code& error, size_t bytes_transferred) {
13✔
271
         if(error) {
13✔
272
            log_error("Server read failed", error);
4✔
273
            stop();
4✔
274
            return;
4✔
275
         }
276

277
         try {
9✔
278
            if(bytes_transferred > 0) {
9✔
279
               log_text_message("Server to client", m_s2p.data(), m_s2p.size());
5✔
280
               log_binary_message("Server to client", m_s2p.data(), m_s2p.size());
5✔
281
               m_tls->send(m_s2p.data(), bytes_transferred);
5✔
282
            }
283
         } catch(Botan::Exception& e) {
×
284
            log_exception("TLS connection failed", e);
×
285
            stop();
×
286
            return;
×
287
         }
×
288

289
         m_s2p.resize(readbuf_size);
9✔
290

291
         m_server_socket.async_read_some(
18✔
292
            boost::asio::buffer(m_s2p),
9✔
293
            boost::asio::bind_executor(
18✔
294
               m_strand, [self = shared_from_this()](const boost::system::error_code& ec, std::size_t bytes) {
108✔
295
                  self->server_read(ec, bytes);
9✔
296
               }));
297
      }
298

299
      void tls_session_activated() override {
4✔
300
         auto onConnect = [self = weak_from_this()](boost::system::error_code ec,
12✔
301
                                                    const tcp::resolver::results_type::iterator& /*endpoint*/) {
302
            if(ec) {
4✔
303
               log_error("Server connection", ec);
×
304
               return;
×
305
            }
306

307
            if(auto ptr = self.lock()) {
4✔
308
               ptr->server_read(boost::system::error_code(), 0);  // start read loop
4✔
309
               ptr->proxy_write_to_server({});
4✔
310
            } else {
311
               log_error("Server connection established, but client session already closed");
×
312
               return;
×
313
            }
4✔
314
         };
4✔
315
         async_connect(m_server_socket, m_server_endpoints.begin(), m_server_endpoints.end(), onConnect);
8✔
316
      }
4✔
317

318
      void tls_session_established(const Botan::TLS::Session_Summary& session) override {
4✔
319
         m_hostname = session.server_info().hostname();
4✔
320
      }
4✔
321

322
      void tls_alert(Botan::TLS::Alert alert) override {
×
323
         if(alert.type() == Botan::TLS::Alert::CloseNotify) {
×
324
            m_tls->close();
×
325
            return;
×
326
         }
327
      }
328

329
      boost::asio::io_context::strand m_strand;
330

331
      tcp::resolver::results_type m_server_endpoints;
332

333
      tcp::socket m_client_socket;
334
      tcp::socket m_server_socket;
335

336
      std::shared_ptr<Botan::RandomNumberGenerator> m_rng;
337
      std::unique_ptr<Botan::TLS::Server> m_tls;
338
      std::string m_hostname;
339

340
      std::vector<uint8_t> m_c2p;
341
      std::vector<uint8_t> m_p2c;
342
      std::vector<uint8_t> m_p2c_pending;
343

344
      std::vector<uint8_t> m_s2p;
345
      std::vector<uint8_t> m_p2s;
346
      std::vector<uint8_t> m_p2s_pending;
347

348
      bool m_is_closed = false;
349
};
350

351
class tls_proxy_server final {
352
   public:
353
      typedef tls_proxy_session session;
354

355
      tls_proxy_server(boost::asio::io_context& io,
1✔
356
                       unsigned short port,
357
                       tcp::resolver::results_type endpoints,
358
                       std::shared_ptr<Botan::Credentials_Manager> creds,
359
                       std::shared_ptr<Botan::TLS::Policy> policy,
360
                       std::shared_ptr<Botan::TLS::Session_Manager> session_mgr,
361
                       size_t max_clients) :
1✔
362
            m_acceptor(io, tcp::endpoint(tcp::v4(), port)),
1✔
363
            m_server_endpoints(std::move(endpoints)),
1✔
364
            m_creds(std::move(creds)),
1✔
365
            m_policy(std::move(policy)),
1✔
366
            m_session_manager(std::move(session_mgr)),
1✔
367
            m_status(max_clients) {
1✔
368
         log_info("Listening for new connections on port " + std::to_string(port));
3✔
369
         serve_one_session();
1✔
370
      }
1✔
371

372
   private:
373
      session::pointer make_session() {
4✔
374
         return session::create(get_io_service(m_acceptor), m_session_manager, m_creds, m_policy, m_server_endpoints);
4✔
375
      }
376

377
      void serve_one_session() {
4✔
378
         const session::pointer new_session = make_session();
4✔
379

380
         m_acceptor.async_accept(
8✔
381
            new_session->client_socket(),
4✔
382
            boost::bind(&tls_proxy_server::handle_accept, this, new_session, boost::asio::placeholders::error));
12✔
383
      }
4✔
384

385
      void handle_accept(const session::pointer& new_session, const boost::system::error_code& error) {
4✔
386
         if(!error) {
4✔
387
            new_session->start();
4✔
388
            m_status.client_serviced();
4✔
389

390
            if(!m_status.should_exit()) {
4✔
391
               serve_one_session();
3✔
392
            }
393
         }
394
      }
4✔
395

396
      tcp::acceptor m_acceptor;
397
      tcp::resolver::results_type m_server_endpoints;
398

399
      std::shared_ptr<Botan::Credentials_Manager> m_creds;
400
      std::shared_ptr<Botan::TLS::Policy> m_policy;
401
      std::shared_ptr<Botan::TLS::Session_Manager> m_session_manager;
402
      ServerStatus m_status;
403
};
404

405
}  // namespace
406

407
class TLS_Proxy final : public Command {
408
   public:
409
      TLS_Proxy() :
2✔
410
            Command(
411
               "tls_proxy listen_port target_host target_port server_cert server_key "
412
               "--policy=default --threads=0 --max-clients=0 --session-db= --session-db-pass=") {}
4✔
413

414
      std::string group() const override { return "tls"; }
1✔
415

416
      std::string description() const override { return "Proxies requests between a TLS client and a TLS server"; }
1✔
417

418
      size_t thread_count() const {
1✔
419
         if(const size_t t = get_arg_sz("threads")) {
1✔
420
            return t;
421
         }
422
   #if defined(BOTAN_HAS_OS_UTILS)
423
         if(const size_t t = Botan::OS::get_cpu_available()) {
1✔
424
            return t;
1✔
425
         }
426
   #endif
427
         return 2;
428
      }
429

430
      void go() override {
1✔
431
         const uint16_t listen_port = get_arg_u16("listen_port");
1✔
432
         const std::string target = get_arg("target_host");
1✔
433
         const std::string target_port = get_arg("target_port");
1✔
434

435
         const std::string server_crt = get_arg("server_cert");
1✔
436
         const std::string server_key = get_arg("server_key");
1✔
437

438
         const size_t num_threads = thread_count();
1✔
439
         const size_t max_clients = get_arg_sz("max-clients");
1✔
440

441
         auto creds = std::make_shared<Basic_Credentials_Manager>(server_crt, server_key);
1✔
442

443
         auto policy = load_tls_policy(get_arg("policy"));
2✔
444

445
         boost::asio::io_context io;
1✔
446

447
         tcp::resolver resolver(io);
1✔
448
         auto server_endpoint_iterator = resolver.resolve(target, target_port);
1✔
449

450
         std::shared_ptr<Botan::TLS::Session_Manager> session_mgr;
1✔
451

452
   #if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER)
453
         const std::string sessions_passphrase = get_passphrase_arg("Session DB passphrase", "session-db-pass");
2✔
454
         const std::string sessions_db = get_arg("session-db");
1✔
455

456
         if(!sessions_db.empty()) {
1✔
457
            session_mgr =
×
458
               std::make_shared<Botan::TLS::Session_Manager_SQLite>(sessions_passphrase, rng_as_shared(), sessions_db);
×
459
         }
460
   #endif
461
         if(!session_mgr) {
1✔
462
            session_mgr = std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng_as_shared());
3✔
463
         }
464

465
         const tls_proxy_server server(
1✔
466
            io, listen_port, server_endpoint_iterator, creds, policy, session_mgr, max_clients);
2✔
467

468
         std::vector<std::shared_ptr<std::thread>> threads;
1✔
469

470
         // run forever... first thread is main calling io.run below
471
         for(size_t i = 2; i <= num_threads; ++i) {
4✔
472
            threads.push_back(std::make_shared<std::thread>([&io]() { io.run(); }));
6✔
473
         }
474

475
         io.run();
1✔
476

477
         for(auto& thread : threads) {
4✔
478
            thread->join();
3✔
479
         }
480
      }
5✔
481
};
482

483
// NOLINTEND(*-avoid-endl,*-avoid-bind)
484

485
BOTAN_REGISTER_COMMAND("tls_proxy", TLS_Proxy);
2✔
486

487
}  // namespace Botan_CLI
488

489
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc