libsshpp v0.1.7
Modern C++17 wrapper for libssh
Loading...
Searching...
No Matches
handler_bridge.ipp
Go to the documentation of this file.
1// SPDX-License-Identifier: LGPL-2.1-or-later
2//
3// Bridges libssh's ssh_server_callbacks_struct / ssh_channel_callbacks_struct
4// C trampolines to SessionHandler/ChannelHandler. See docs/design/08 §8.6.
5//
6// Every trampoline is wrapped in try/catch: exceptions must never propagate
7// into libssh's C frames (they would corrupt its internal state machine).
8#pragma once
9
13
14#include <libssh/callbacks.h>
15#include <libssh/libssh.h>
16#include <libssh/server.h>
17
18#include <unordered_map>
19#include <vector>
20
21namespace sshpp::detail {
22
24public:
25 HandlerBridge(server::Session& session, std::shared_ptr<server::SessionHandler> handler)
26 : session_(&session), handler_(std::move(handler)) {
27 ssh_callbacks_init((&server_cb_));
28 server_cb_.userdata = this;
29 server_cb_.auth_none_function = &HandlerBridge::auth_none_cb;
30 server_cb_.auth_password_function = &HandlerBridge::auth_password_cb;
31 server_cb_.auth_pubkey_function = &HandlerBridge::auth_pubkey_cb;
32 server_cb_.auth_gssapi_mic_function = &HandlerBridge::auth_gssapi_mic_cb;
33 server_cb_.channel_open_request_session_function = &HandlerBridge::channel_open_session_cb;
34 server_cb_.service_request_function = &HandlerBridge::service_request_cb;
35 }
36
37 ~HandlerBridge() = default;
38 HandlerBridge(const HandlerBridge&) = delete;
40
42 if (ssh_set_server_callbacks(raw, &server_cb_) != SSH_OK) {
43 return ErrorInfo{make_error_code(errc::unknown), "", "ssh_set_server_callbacks"};
44 }
45 return {};
46 }
47
49 if (!handler_) return;
50 try {
51 handler_->on_disconnect(*session_);
52 } catch (...) {
53 }
54 }
55
60 for (auto raw : pending_removals_) channels_.erase(raw);
61 pending_removals_.clear();
62 }
63
64private:
65 struct ChannelState {
66 Channel channel;
67 std::shared_ptr<server::ChannelHandler> handler;
68 ssh_channel_callbacks_struct cb{};
69 HandlerBridge* bridge = nullptr;
70 };
71
72 void report_error(const ErrorInfo& info) {
73 if (!handler_) return;
74 try {
75 handler_->on_error(*session_, info);
76 } catch (...) {
77 }
78 }
79
80 ChannelState* register_channel(native_channel raw, std::shared_ptr<server::ChannelHandler> handler) {
81 auto state = std::make_unique<ChannelState>();
82 state->channel = Channel::from_native(raw, session_->core_, Ownership::owning);
83 state->handler = std::move(handler);
84 state->bridge = this;
85 ssh_callbacks_init((&state->cb));
86 state->cb.userdata = state.get();
87 state->cb.channel_data_function = &HandlerBridge::channel_data_cb;
88 state->cb.channel_eof_function = &HandlerBridge::channel_eof_cb;
89 state->cb.channel_close_function = &HandlerBridge::channel_close_cb;
90 state->cb.channel_signal_function = &HandlerBridge::channel_signal_cb;
91 state->cb.channel_pty_request_function = &HandlerBridge::channel_pty_request_cb;
92 state->cb.channel_shell_request_function = &HandlerBridge::channel_shell_request_cb;
93 state->cb.channel_x11_req_function = &HandlerBridge::channel_x11_req_cb;
94 state->cb.channel_pty_window_change_function = &HandlerBridge::channel_pty_window_change_cb;
95 state->cb.channel_exec_request_function = &HandlerBridge::channel_exec_request_cb;
96 state->cb.channel_env_request_function = &HandlerBridge::channel_env_request_cb;
97 state->cb.channel_subsystem_request_function = &HandlerBridge::channel_subsystem_request_cb;
98 ssh_set_channel_callbacks(raw, &state->cb);
99 ChannelState* ptr = state.get();
100 channels_[raw] = std::move(state);
101 return ptr;
102 }
103
104 // ---- auth trampolines --------------------------------------------------------
105 static int auth_none_cb(ssh_session, const char* user, void* userdata) {
106 auto* self = static_cast<HandlerBridge*>(userdata);
107 try {
108 auto r = self->handler_->on_auth_none(*self->session_, user != nullptr ? user : "");
109 return self->apply_auth_result(r, user);
110 } catch (const std::exception& e) {
111 self->report_error(ErrorInfo{make_error_code(errc::unknown), e.what(), "on_auth_none"});
112 return SSH_AUTH_DENIED;
113 } catch (...) {
114 return SSH_AUTH_DENIED;
115 }
116 }
117
118 static int auth_password_cb(ssh_session, const char* user, const char* password, void* userdata) {
119 auto* self = static_cast<HandlerBridge*>(userdata);
120 try {
121 auto r = self->handler_->on_auth_password(*self->session_, user != nullptr ? user : "",
122 SecureString(password != nullptr ? password : ""));
123 return self->apply_auth_result(r, user);
124 } catch (const std::exception& e) {
125 self->report_error(ErrorInfo{make_error_code(errc::unknown), e.what(), "on_auth_password"});
126 return SSH_AUTH_DENIED;
127 } catch (...) {
128 return SSH_AUTH_DENIED;
129 }
130 }
131
132 static int auth_pubkey_cb(ssh_session, const char* user, ssh_key pubkey, char signature_state, void* userdata) {
133 auto* self = static_cast<HandlerBridge*>(userdata);
134 try {
136 if (signature_state == SSH_PUBLICKEY_STATE_VALID) state = server::PublicKeyState::valid;
137 else if (signature_state == SSH_PUBLICKEY_STATE_WRONG) state = server::PublicKeyState::wrong;
138 Key key = Key::from_native(pubkey, Ownership::borrowed);
139 auto r = self->handler_->on_auth_public_key(*self->session_, user != nullptr ? user : "", key, state);
140 // A `state == none` success is just "this key would be acceptable" (a
141 // probe before the client signs anything); only a `state == valid`
142 // success is an actually-verified signature that finishes auth.
143 return self->apply_auth_result(r, user, /*finalize=*/state == server::PublicKeyState::valid);
144 } catch (const std::exception& e) {
145 self->report_error(ErrorInfo{make_error_code(errc::unknown), e.what(), "on_auth_public_key"});
146 return SSH_AUTH_DENIED;
147 } catch (...) {
148 return SSH_AUTH_DENIED;
149 }
150 }
151
152 static int auth_gssapi_mic_cb(ssh_session, const char* user, const char* principal, void* userdata) {
153 auto* self = static_cast<HandlerBridge*>(userdata);
154 try {
155 auto r = self->handler_->on_auth_gssapi_mic(*self->session_, user != nullptr ? user : "",
156 principal != nullptr ? principal : "");
157 return self->apply_auth_result(r, user);
158 } catch (...) {
159 return SSH_AUTH_DENIED;
160 }
161 }
162
163 int apply_auth_result(server::AuthResult r, const char* user, bool finalize = true) {
165 if (finalize) {
166 session_->authenticated_ = true;
167 session_->user_ = user != nullptr ? user : "";
168 }
169 return SSH_AUTH_SUCCESS;
170 }
171 if (r == server::AuthResult::partial) return SSH_AUTH_PARTIAL;
172 return SSH_AUTH_DENIED;
173 }
174
175 // ---- channel-open / service ---------------------------------------------------
176 static ssh_channel channel_open_session_cb(ssh_session sess, void* userdata) {
177 auto* self = static_cast<HandlerBridge*>(userdata);
178 try {
179 auto ch_handler = self->handler_->on_channel_open_session(*self->session_);
180 if (!ch_handler) return nullptr;
181 ssh_channel raw = ssh_channel_new(sess);
182 if (raw == nullptr) return nullptr;
183 self->register_channel(raw, std::move(ch_handler));
184 return raw;
185 } catch (const std::exception& e) {
186 self->report_error(ErrorInfo{make_error_code(errc::unknown), e.what(), "on_channel_open_session"});
187 return nullptr;
188 } catch (...) {
189 return nullptr;
190 }
191 }
192
193 static int service_request_cb(ssh_session, const char* service, void* userdata) {
194 auto* self = static_cast<HandlerBridge*>(userdata);
195 try {
196 self->handler_->on_service_request(*self->session_, service != nullptr ? service : "");
197 } catch (...) {
198 }
199 return 0;
200 }
201
202 // ---- channel-level trampolines -------------------------------------------------
203 static int channel_data_cb(ssh_session, ssh_channel, void* data, uint32_t len, int is_stderr, void* userdata) {
204 auto* state = static_cast<ChannelState*>(userdata);
205 try {
206 auto consumed = state->handler->on_data(state->channel,
207 ByteView(static_cast<const std::byte*>(data), len),
208 is_stderr != 0 ? Stream::stderr_ : Stream::stdout_);
209 return static_cast<int>(consumed);
210 } catch (...) {
211 return 0;
212 }
213 }
214
215 static void channel_eof_cb(ssh_session, ssh_channel, void* userdata) {
216 auto* state = static_cast<ChannelState*>(userdata);
217 try {
218 state->handler->on_eof(state->channel);
219 } catch (...) {
220 }
221 }
222
223 static void channel_close_cb(ssh_session, ssh_channel raw, void* userdata) {
224 auto* state = static_cast<ChannelState*>(userdata);
225 try {
226 state->handler->on_close(state->channel);
227 } catch (...) {
228 }
229 // Deferred: freeing the Channel (via erase, destroying the owning
230 // ChannelState) here would call ssh_channel_free() while
231 // ssh_event_dopoll() is still mid-iteration over its channel list for
232 // this very session, which corrupted libssh's internal state and
233 // crashed the *next* dopoll() call. Collect it and let
234 // reap_closed_channels() (called from Session::try_poll(), after
235 // dopoll() has returned) do the actual erase/free instead.
236 state->bridge->pending_removals_.push_back(raw);
237 }
238
239 static void channel_signal_cb(ssh_session, ssh_channel, const char* signal, void* userdata) {
240 auto* state = static_cast<ChannelState*>(userdata);
241 try {
242 state->handler->on_signal(state->channel, signal != nullptr ? signal : "");
243 } catch (...) {
244 }
245 }
246
247 static int channel_pty_request_cb(ssh_session, ssh_channel, const char* term, int width, int height,
248 int, int, void* userdata) {
249 auto* state = static_cast<ChannelState*>(userdata);
250 try {
251 PtySize size{width, height};
252 return state->handler->on_pty_request(state->channel, term != nullptr ? term : "", size) ? 0 : -1;
253 } catch (...) {
254 return -1;
255 }
256 }
257
258 static int channel_pty_window_change_cb(ssh_session, ssh_channel, int width, int height, int, int,
259 void* userdata) {
260 auto* state = static_cast<ChannelState*>(userdata);
261 try {
262 PtySize size{width, height};
263 return state->handler->on_pty_resize(state->channel, size) ? 0 : -1;
264 } catch (...) {
265 return -1;
266 }
267 }
268
269 static int channel_shell_request_cb(ssh_session, ssh_channel, void* userdata) {
270 auto* state = static_cast<ChannelState*>(userdata);
271 try {
272 return state->handler->on_shell_request(state->channel) ? 0 : 1;
273 } catch (...) {
274 return 1;
275 }
276 }
277
278 static int channel_exec_request_cb(ssh_session, ssh_channel, const char* command, void* userdata) {
279 auto* state = static_cast<ChannelState*>(userdata);
280 try {
281 return state->handler->on_exec_request(state->channel, command != nullptr ? command : "") ? 0 : 1;
282 } catch (...) {
283 return 1;
284 }
285 }
286
287 static int channel_env_request_cb(ssh_session, ssh_channel, const char* name, const char* value,
288 void* userdata) {
289 auto* state = static_cast<ChannelState*>(userdata);
290 try {
291 return state->handler->on_env_request(state->channel, name != nullptr ? name : "",
292 value != nullptr ? value : "")
293 ? 0
294 : 1;
295 } catch (...) {
296 return 1;
297 }
298 }
299
300 static int channel_subsystem_request_cb(ssh_session, ssh_channel raw, const char* name, void* userdata) {
301 auto* state = static_cast<ChannelState*>(userdata);
302 try {
303 bool accepted = state->handler->on_subsystem_request(state->channel, name != nullptr ? name : "");
304 if (accepted) {
305 // Subsystems (sftp and friends) hand the channel off to a
306 // dedicated protocol handler that does its own raw,
307 // synchronous ssh_channel_read()s (see SftpSubsystemHandler).
308 // Stop also dispatching this channel's data through
309 // channel_data_function, or the two readers race for bytes.
310 ssh_remove_channel_callbacks(raw, &state->cb);
311 }
312 return accepted ? 0 : 1;
313 } catch (...) {
314 return 1;
315 }
316 }
317
318 static void channel_x11_req_cb(ssh_session, ssh_channel, int single_connection, const char* auth_protocol,
319 const char* auth_cookie, uint32_t screen_number, void* userdata) {
320 auto* state = static_cast<ChannelState*>(userdata);
321 try {
322 state->handler->on_x11_request(state->channel, single_connection != 0,
323 auth_protocol != nullptr ? auth_protocol : "",
324 auth_cookie != nullptr ? auth_cookie : "", screen_number);
325 } catch (...) {
326 }
327 }
328
329 server::Session* session_;
330 std::shared_ptr<server::SessionHandler> handler_;
331 ssh_server_callbacks_struct server_cb_{};
332 std::unordered_map<ssh_channel, std::unique_ptr<ChannelState>> channels_;
333 std::vector<ssh_channel> pending_removals_;
334};
335
336} // namespace sshpp::detail
Definition channel.hpp:36
static Channel from_native(native_channel, detail::SessionCorePtr, Ownership)
Definition channel.ipp:74
static Key from_native(native_key, Ownership)
Definition key.ipp:111
Definition result.hpp:16
Definition handler_bridge.ipp:23
HandlerBridge(const HandlerBridge &)=delete
void reap_closed_channels()
Definition handler_bridge.ipp:59
HandlerBridge(server::Session &session, std::shared_ptr< server::SessionHandler > handler)
Definition handler_bridge.ipp:25
Result< void > install(native_session raw)
Definition handler_bridge.ipp:41
void notify_disconnect()
Definition handler_bridge.ipp:48
HandlerBridge & operator=(const HandlerBridge &)=delete
One accepted server-side connection. See docs/design/08 §8.4.
Definition server_session.hpp:32
const PassphraseCallback * cb
Definition key.ipp:68
Definition handler_bridge.hpp:10
AuthResult
Definition handlers.hpp:38
PublicKeyState
Definition message.hpp:27
::ssh_channel_struct * native_channel
Definition native_fwd.hpp:26
SSHPP_INLINE std::error_code make_error_code(errc e) noexcept
Definition error.ipp:133
::ssh_session_struct * native_session
Definition native_fwd.hpp:25
Definition error.hpp:110
Definition error.hpp:119