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

randombit / botan / 13639173614

03 Mar 2025 07:48PM UTC coverage: 91.683% (-0.004%) from 91.687%
13639173614

Pull #4660

github

web-flow
Merge d21679088 into 583501358
Pull Request #4660: Consolidation and Enhancement of BSD Socket Layer

95828 of 104521 relevant lines covered (91.68%)

11146175.33 hits per line

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

70.45
/src/cli/tls_server.cpp
1
/*
2
* TLS echo server using BSD sockets
3
* (C) 2014 Jack Lloyd
4
*     2017 René Korthaus, Rohde & Schwarz Cybersecurity
5
*     2023 René Meusel, Rohde & Schwarz Cybersecurity
6
      2025 Kagan Can Sit
7
*
8
* Botan is released under the Simplified BSD License (see license.txt)
9
*/
10

11
#include "cli.h"
12
#include "sandbox.h"
13

14
#include <botan/internal/target_info.h>
15

16
#if defined(BOTAN_TARGET_OS_HAS_SOCKETS)
17
   #include <sys/socket.h>
18
#endif
19

20
#if defined(BOTAN_HAS_TLS) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(BOTAN_TARGET_OS_HAS_SOCKETS)
21

22
   #if defined(SO_MARK) || defined(SO_USER_COOKIE) || defined(SO_RTABLE)
23
      #if defined(SO_MARK)
24
         #define BOTAN_SO_SOCKETID SO_MARK
25
      #elif defined(SO_USER_COOKIE)
26
         #define BOTAN_SO_SOCKETID SO_USER_COOKIE
27
      #else
28
         #define BOTAN_SO_SOCKETID SO_RTABLE
29
      #endif
30
   #endif
31

32
   #include <botan/hex.h>
33
   #include <botan/mem_ops.h>
34
   #include <botan/tls_callbacks.h>
35
   #include <botan/tls_policy.h>
36
   #include <botan/tls_server.h>
37
   #include <botan/tls_session_manager_memory.h>
38

39
   #include <chrono>
40
   #include <fstream>
41
   #include <list>
42
   #include <memory>
43

44
   #include "tls_helpers.h"
45
   #include <botan/internal/socket_platform.h>
46

47
namespace Botan_CLI {
48

49
class TLS_Server;
50

51
namespace {
52

53
class Callbacks : public Botan::TLS::Callbacks {
1✔
54
   public:
55
      Callbacks(TLS_Server& server_command) : m_server_command(server_command) {}
1✔
56

57
      std::ostream& output();
58
      void send(std::span<const uint8_t> buffer);
59
      void push_pending_output(std::string line);
60

61
      void tls_session_established(const Botan::TLS::Session_Summary& session) override {
9✔
62
         output() << "Handshake complete, " << session.version().to_string() << " using "
9✔
63
                  << session.ciphersuite().to_string();
27✔
64

65
         if(const auto& psk = session.external_psk_identity()) {
9✔
66
            output() << " (utilized PSK identity: " << maybe_hex_encode(psk.value()) << ")";
6✔
67
         }
68

69
         output() << std::endl;
9✔
70

71
         if(const auto& session_id = session.session_id(); !session_id.empty()) {
9✔
72
            output() << "Session ID " << Botan::hex_encode(session_id.get()) << std::endl;
9✔
73
         }
74

75
         if(const auto& session_ticket = session.session_ticket()) {
9✔
76
            output() << "Session ticket " << Botan::hex_encode(session_ticket->get()) << std::endl;
×
77
         }
78
      }
9✔
79

80
      void tls_record_received(uint64_t /*seq_no*/, std::span<const uint8_t> input) override {
9✔
81
         for(size_t i = 0; i != input.size(); ++i) {
2,061✔
82
            const char c = static_cast<char>(input[i]);
2,052✔
83
            m_line_buf += c;
2,052✔
84
            if(c == '\n') {
2,052✔
85
               push_pending_output(std::exchange(m_line_buf, {}));
27✔
86
            }
87
         }
88
      }
9✔
89

90
      void tls_emit_data(std::span<const uint8_t> buf) override { send(buf); }
132✔
91

92
      void tls_alert(Botan::TLS::Alert alert) override { output() << "Alert: " << alert.type_string() << std::endl; }
18✔
93

94
      std::string tls_server_choose_app_protocol(const std::vector<std::string>& /*client_protos*/) override {
×
95
         // we ignore whatever the client sends here
96
         return "echo/0.1";
×
97
      }
98

99
      void tls_verify_raw_public_key(const Botan::Public_Key& raw_public_key,
×
100
                                     Botan::Usage_Type /* usage */,
101
                                     std::string_view /* hostname */,
102
                                     const Botan::TLS::Policy& /* policy */) override {
103
         const auto fingerprint = raw_public_key.fingerprint_public("SHA-256");
×
104
         output() << "received Raw Public Key (" << fingerprint << ")\n";
×
105
      }
×
106

107
   private:
108
      TLS_Server& m_server_command;
109
      std::string m_line_buf;
110
};
111

112
}  // namespace
113

114
class TLS_Server final : public Command {
115
   public:
116
   #if defined(BOTAN_SO_SOCKETID)
117
      TLS_Server() :
2✔
118
            Command(
119
               "tls_server cert-or-pubkey key --port=443 --psk= --psk-identity= --psk-prf=SHA-256 --type=tcp --policy=default --dump-traces= --max-clients=0 --socket-id=0")
4✔
120
   #else
121
      TLS_Server() :
122
            Command(
123
               "tls_server cert-or-pubkey key --port=443 --psk= --psk-identity= --psk-prf=SHA-256 --type=tcp --policy=default --dump-traces= --max-clients=0")
124
   #endif
125
      {
126
         Botan::OS::Socket_Platform::socket_init();
2✔
127
      }
2✔
128

129
      ~TLS_Server() override {
4✔
130
         Botan::OS::Socket_Platform::socket_fini();
2✔
131
      }
4✔
132

133
      TLS_Server(const TLS_Server& other) = delete;
134
      TLS_Server(TLS_Server&& other) = delete;
135
      TLS_Server& operator=(const TLS_Server& other) = delete;
136
      TLS_Server& operator=(TLS_Server&& other) = delete;
137

138
      std::string group() const override {
1✔
139
         return "tls";
1✔
140
      }
141

142
      std::string description() const override {
1✔
143
         return "Accept TLS/DTLS connections from TLS/DTLS clients";
1✔
144
      }
145

146
      void go() override {
1✔
147
         const std::string server_cred = get_arg("cert-or-pubkey");
1✔
148
         const std::string server_key = get_arg("key");
1✔
149
         const uint16_t port = get_arg_u16("port");
1✔
150
         const size_t max_clients = get_arg_sz("max-clients");
1✔
151
         const std::string transport = get_arg("type");
1✔
152
         const std::string dump_traces_to = get_arg("dump-traces");
1✔
153
         auto psk = [this]() -> std::optional<Botan::secure_vector<uint8_t>> {
×
154
            auto psk_hex = get_arg_maybe("psk");
1✔
155
            if(psk_hex) {
1✔
156
               return Botan::hex_decode_locked(psk_hex.value());
1✔
157
            } else {
158
               return {};
×
159
            }
160
         }();
2✔
161
         const std::optional<std::string> psk_identity = get_arg_maybe("psk-identity");
1✔
162
         const std::optional<std::string> psk_prf = get_arg_maybe("psk-prf");
1✔
163

164
   #if defined(BOTAN_SO_SOCKETID)
165
         m_socket_id = static_cast<uint32_t>(get_arg_sz("socket-id"));
1✔
166
   #endif
167

168
         if(transport != "tcp" && transport != "udp") {
1✔
169
            throw CLI_Usage_Error("Invalid transport type '" + transport + "' for TLS");
×
170
         }
171

172
         m_is_tcp = (transport == "tcp");
1✔
173

174
         auto policy = load_tls_policy(get_arg("policy"));
2✔
175
         auto session_manager =
1✔
176
            std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng_as_shared());  // TODO sqlite3
2✔
177
         auto creds =
1✔
178
            std::make_shared<Basic_Credentials_Manager>(server_cred, server_key, std::move(psk), psk_identity, psk_prf);
1✔
179
         auto callbacks = std::make_shared<Callbacks>(*this);
1✔
180

181
         if(!m_sandbox.init()) {
1✔
182
            error_output() << "Failed sandboxing\n";
×
183
            return;
×
184
         }
185

186
         socket_type server_fd = make_server_socket(port);
1✔
187
         size_t clients_served = 0;
1✔
188

189
         output() << "Listening for new connections on " << transport << " port " << port << std::endl;
1✔
190

191
         while(true) {
21✔
192
            if(max_clients > 0 && clients_served >= max_clients) {
11✔
193
               break;
194
            }
195

196
            if(m_is_tcp) {
10✔
197
               m_socket = ::accept(server_fd, nullptr, nullptr);
10✔
198
            } else {
199
               struct sockaddr_in from;
×
200
               socklen_t from_len = sizeof(sockaddr_in);
×
201

202
               void* peek_buf = nullptr;
×
203
               size_t peek_len = 0;
×
204

205
   #if defined(BOTAN_TARGET_OS_IS_MACOS)
206
               // macOS handles zero size buffers differently - it will return 0 even if there's no incoming data,
207
               // and after that connect() will fail as sockaddr_in from is not initialized
208
               int dummy;
209
               peek_buf = &dummy;
210
               peek_len = sizeof(dummy);
211
   #endif
212

213
               if(::recvfrom(server_fd,
×
214
                             static_cast<char*>(peek_buf),
215
                             static_cast<sendrecv_len_type>(peek_len),
216
                             MSG_PEEK,
217
                             reinterpret_cast<struct sockaddr*>(&from),
218
                             &from_len) != 0) {
219
                  throw CLI_Error("Could not peek next packet");
×
220
               }
221

222
               if(::connect(server_fd, reinterpret_cast<struct sockaddr*>(&from), from_len) != 0) {
×
223
                  throw CLI_Error("Could not connect UDP socket");
×
224
               }
225
               m_socket = server_fd;
×
226
            }
227

228
            clients_served++;
10✔
229

230
            Botan::TLS::Server server(callbacks, session_manager, creds, policy, rng_as_shared(), m_is_tcp == false);
60✔
231

232
            std::unique_ptr<std::ostream> dump_stream;
10✔
233

234
            if(!dump_traces_to.empty()) {
10✔
235
               auto now = std::chrono::system_clock::now().time_since_epoch();
×
236
               uint64_t timestamp = std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
×
237
               const std::string dump_file = dump_traces_to + "/tls_" + std::to_string(timestamp) + ".bin";
×
238
               dump_stream = std::make_unique<std::ofstream>(dump_file.c_str());
×
239
            }
×
240

241
            try {
242
               while(!server.is_closed()) {
54✔
243
                  try {
44✔
244
                     uint8_t buf[4 * 1024] = {0};
44✔
245
                     ssize_t got = ::recv(m_socket, Botan::cast_uint8_ptr_to_char(buf), sizeof(buf), 0);
44✔
246

247
                     if(got == -1) {
44✔
248
                        error_output() << "Error in socket read - "
×
249
                                       << Botan::OS::Socket_Platform::get_last_socket_error() << std::endl;
×
250
                        break;
×
251
                     }
252

253
                     if(got == 0) {
44✔
254
                        error_output() << "EOF on socket" << std::endl;
×
255
                        break;
256
                     }
257

258
                     if(dump_stream) {
44✔
259
                        dump_stream->write(reinterpret_cast<const char*>(buf), got);
×
260
                     }
261

262
                     server.received_data(buf, got);
44✔
263

264
                     while(server.is_active() && !m_pending_output.empty()) {
52✔
265
                        std::string output = m_pending_output.front();
9✔
266
                        m_pending_output.pop_front();
9✔
267
                        server.send(output);
9✔
268

269
                        if(output == "quit\n") {
9✔
270
                           server.close();
×
271
                        }
272
                     }
9✔
273
                  } catch(std::exception& e) {
1✔
274
                     error_output() << "Connection problem: " << e.what() << std::endl;
1✔
275
                     if(m_is_tcp) {
1✔
276
                        Botan::OS::Socket_Platform::close_socket(m_socket);
1✔
277
                        m_socket = Botan::OS::Socket_Platform::invalid_socket();
1✔
278
                     }
279
                  }
1✔
280
               }
281
            } catch(Botan::Exception& e) {
×
282
               error_output() << "Connection failed: " << e.what() << "\n";
×
283
            }
×
284

285
            if(m_is_tcp) {
10✔
286
               Botan::OS::Socket_Platform::close_socket(m_socket);
10✔
287
               m_socket = Botan::OS::Socket_Platform::invalid_socket();
10✔
288
            }
289
         }
10✔
290

291
         Botan::OS::Socket_Platform::close_socket(server_fd);
2✔
292
      }
6✔
293

294
   public:
295
      using Command::flag_set;
296
      using Command::output;
297

298
      void send(std::span<const uint8_t> buf) {
66✔
299
         if(m_is_tcp) {
66✔
300
            ssize_t sent = ::send(m_socket, buf.data(), static_cast<sendrecv_len_type>(buf.size()), MSG_NOSIGNAL);
66✔
301

302
            if(sent == -1) {
66✔
303
               error_output() << "Error writing to socket - " << Botan::OS::Socket_Platform::get_last_socket_error()
×
304
                              << std::endl;
×
305
            } else if(sent != static_cast<ssize_t>(buf.size())) {
66✔
306
               error_output() << "Packet of length " << buf.size() << " truncated to " << sent << std::endl;
×
307
            }
308
         } else {
309
            while(!buf.empty()) {
×
310
               ssize_t sent = ::send(m_socket, buf.data(), static_cast<sendrecv_len_type>(buf.size()), MSG_NOSIGNAL);
×
311

312
               if(sent == -1) {
×
313
                  if(errno == EINTR) {
×
314
                     sent = 0;
315
                  } else {
316
                     throw CLI_Error("Socket write failed");
×
317
                  }
318
               }
319

320
               buf = buf.subspan(sent);
×
321
            }
322
         }
323
      }
66✔
324

325
      void push_pending_output(std::string line) {
9✔
326
         m_pending_output.emplace_back(std::move(line));
18✔
327
      }
9✔
328

329
   private:
330
      using socket_type = Botan::OS::Socket_Platform::socket_type;
331
      using socket_op_ret_type = Botan::OS::Socket_Platform::socket_op_ret_type;
332
      using socklen_type = Botan::OS::Socket_Platform::socklen_type;
333
      using sendrecv_len_type = Botan::OS::Socket_Platform::sendrecv_len_type;
334

335
      socket_type make_server_socket(uint16_t port) {
1✔
336
         const int type = m_is_tcp ? SOCK_STREAM : SOCK_DGRAM;
1✔
337

338
         socket_type fd = ::socket(PF_INET, type, 0);
1✔
339
         if(fd == Botan::OS::Socket_Platform::invalid_socket()) {
1✔
340
            throw CLI_Error("Unable to acquire socket");
×
341
         }
342

343
         sockaddr_in socket_info;
1✔
344
         Botan::clear_mem(&socket_info, 1);
1✔
345
         socket_info.sin_family = AF_INET;
1✔
346
         socket_info.sin_port = htons(port);
1✔
347

348
         // FIXME: support limiting listeners
349
         socket_info.sin_addr.s_addr = INADDR_ANY;
1✔
350

351
         if(::bind(fd, reinterpret_cast<struct sockaddr*>(&socket_info), sizeof(struct sockaddr)) != 0) {
1✔
352
            Botan::OS::Socket_Platform::close_socket(fd);
×
353
            throw CLI_Error("server bind failed");
×
354
         }
355

356
         if(m_is_tcp) {
1✔
357
            constexpr int backlog = std::min(100, SOMAXCONN);
1✔
358
            if(::listen(fd, backlog) != 0) {
1✔
359
               Botan::OS::Socket_Platform::close_socket(fd);
×
360
               throw CLI_Error("listen failed");
×
361
            }
362
         }
363
         if(m_socket_id > 0) {
1✔
364
   #if defined(BOTAN_SO_SOCKETID)
365
            if(::setsockopt(fd,
×
366
                            SOL_SOCKET,
367
                            BOTAN_SO_SOCKETID,
368
                            reinterpret_cast<const void*>(&m_socket_id),
×
369
                            sizeof(m_socket_id)) != 0) {
370
               // Failed but not world-ending issue
371
               output() << "set socket identifier setting failed" << std::endl;
×
372
            }
373
   #endif
374
         }
375
         return fd;
1✔
376
      }
377

378
      socket_type m_socket = Botan::OS::Socket_Platform::invalid_socket();
379
      bool m_is_tcp = false;
380
      uint32_t m_socket_id = 0;
381
      std::list<std::string> m_pending_output;
382
      Sandbox m_sandbox;
383
};
384

385
namespace {
386

387
std::ostream& Callbacks::output() {
38✔
388
   return m_server_command.output();
38✔
389
}
390

391
void Callbacks::send(std::span<const uint8_t> buffer) {
66✔
392
   m_server_command.send(buffer);
66✔
393
}
394

395
void Callbacks::push_pending_output(std::string line) {
9✔
396
   m_server_command.push_pending_output(std::move(line));
18✔
397
}
9✔
398

399
}  // namespace
400

401
BOTAN_REGISTER_COMMAND("tls_server", TLS_Server);
2✔
402

403
}  // namespace Botan_CLI
404

405
#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