ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
ssh_channel.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file ssh_channel.cpp
6 * @brief SSH connection protocol - multiplexed session channels (RFC 4254).
7 *
8 * The channel table is owned here; inbound messages are routed to a channel by the
9 * recipient channel id they carry, and the local channel id is the channel's slot
10 * index in its connection's pool (unique per connection, which is all RFC 4254
11 * requires). Other layers go through these functions, never the table.
12 */
13
16#include <string.h>
17
19
20// All SSH channel-layer callbacks, owned by one instance (internal linkage): the channel-data
21// sink and the local/remote port-forward hooks. Grouped so it is one named owner, unreachable
22// from any other translation unit. (The ssh_chan[][] table is the shared cross-TU substrate.)
24{
31#if PC_ENABLE_SSH_SFTP
32 SshSftpOpenCb pc_sftp_open_cb = nullptr;
33 SshSftpDataCb pc_sftp_data_cb = nullptr;
34#endif
35#if PC_ENABLE_SSH_SCP
36 SshScpOpenCb pc_scp_open_cb = nullptr;
37 SshScpDataCb pc_scp_data_cb = nullptr;
38#endif
39};
40static SshChannelCtx s_chcb;
41
43{
44 s_chcb.data_cb = cb;
45}
46
47#if PC_ENABLE_SSH_SFTP
48void pc_ssh_channel_set_sftp_open_cb(SshSftpOpenCb cb)
49{
50 s_chcb.pc_sftp_open_cb = cb;
51}
52void pc_ssh_channel_set_sftp_data_cb(SshSftpDataCb cb)
53{
54 s_chcb.pc_sftp_data_cb = cb;
55}
56#endif
57
58#if PC_ENABLE_SSH_SCP
59void pc_ssh_channel_set_scp_open_cb(SshScpOpenCb cb)
60{
61 s_chcb.pc_scp_open_cb = cb;
62}
63void pc_ssh_channel_set_scp_data_cb(SshScpDataCb cb)
64{
65 s_chcb.pc_scp_data_cb = cb;
66}
67#endif
68
73
78
83
88
93
94void pc_ssh_channel_init(uint8_t i)
95{
96 if (i >= MAX_SSH_CONNS)
97 {
98 return;
99 }
100 memset(ssh_chan[i], 0, sizeof(ssh_chan[i])); // reset every channel for this connection
101}
102
103// ---------------------------------------------------------------------------
104// Wire helpers
105// ---------------------------------------------------------------------------
106
107static uint32_t rd_u32(const uint8_t *p)
108{
109 return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3];
110}
111
112static void wr_u32(uint8_t *p, uint32_t v)
113{
114 p[0] = (uint8_t)(v >> 24);
115 p[1] = (uint8_t)(v >> 16);
116 p[2] = (uint8_t)(v >> 8);
117 p[3] = (uint8_t)v;
118}
119
120// Read an SSH string: out points at the data, *slen its length. Advances *off.
121static bool rd_string(const uint8_t *p, size_t len, size_t *off, const uint8_t **out, uint32_t *slen)
122{
123 if (*off + 4 > len)
124 {
125 return false;
126 }
127 uint32_t n = rd_u32(p + *off);
128 *off += 4;
129 if (*off + n > len)
130 {
131 return false;
132 }
133 *out = p + *off;
134 *slen = n;
135 *off += n;
136 return true;
137}
138
139// ---------------------------------------------------------------------------
140// Channel table (owned here)
141// ---------------------------------------------------------------------------
142
143// The open channel @p id on connection @p i, or nullptr. local id == slot index.
144static SshChannel *chan_by_id(uint8_t i, uint32_t id)
145{
146 // GCOVR_EXCL_LINE below: i is always in range - every caller rejects i >= MAX_SSH_CONNS before
147 // looking a channel up, so only the id / open halves of this guard are exercisable.
148 if (i >= MAX_SSH_CONNS || id >= PC_SSH_MAX_CHANNELS || !ssh_chan[i][id].open) // GCOVR_EXCL_LINE
149 {
150 return nullptr;
151 }
152 return &ssh_chan[i][id];
153}
154
155// A pending server-initiated channel @p id on connection @p i (awaiting the client's
156// CONFIRMATION / FAILURE), or nullptr.
157static SshChannel *chan_pending_by_id(uint8_t i, uint32_t id)
158{
159 // GCOVR_EXCL_LINE below: i is always in range (same reason as chan_by_id above).
160 if (i >= MAX_SSH_CONNS || id >= PC_SSH_MAX_CHANNELS || !ssh_chan[i][id].pending) // GCOVR_EXCL_LINE
161 {
162 return nullptr;
163 }
164 return &ssh_chan[i][id];
165}
166
167// First free channel slot on connection @p i, or -1 if the pool is full. A pending
168// (opened-but-unconfirmed) channel is in use just like an open one.
169static int chan_alloc(uint8_t i)
170{
171 for (int c = 0; c < PC_SSH_MAX_CHANNELS; c++)
172 {
173 if (!ssh_chan[i][c].open && !ssh_chan[i][c].pending)
174 {
175 return c;
176 }
177 }
178 return -1;
179}
180
181// ---------------------------------------------------------------------------
182// CHANNEL_OPEN → CONFIRMATION / FAILURE
183// ---------------------------------------------------------------------------
184
185// CHANNEL_OPEN_FAILURE: byte || recipient(sender) || reason || desc || lang.
186// reason: 1 admin-prohibited, 2 connect-failed, 3 unknown-type, 4 resource-shortage.
187static int build_open_failure(uint8_t *out, size_t cap, uint32_t sender, uint32_t reason, size_t *out_len)
188{
189 if (cap < 17)
190 {
191 return -1;
192 }
194 wr_u32(out + 1, sender);
195 wr_u32(out + 5, reason);
196 wr_u32(out + 9, 0); // empty description
197 wr_u32(out + 13, 0); // empty language
198 *out_len = 17;
199 return 0;
200}
201
202// CHANNEL_OPEN_CONFIRMATION: byte || recipient(peer) || sender(local) || window || max.
203static int build_open_confirm(const SshChannel *c, uint8_t *out, size_t cap, size_t *out_len)
204{
205 if (cap < 17)
206 {
207 return -1;
208 }
210 wr_u32(out + 1, c->peer_id);
211 wr_u32(out + 5, c->local_id);
212 wr_u32(out + 9, c->local_window);
213 wr_u32(out + 13, SSH_CHAN_MAX_PACKET);
214 *out_len = 17;
215 return 0;
216}
217
218// ---------------------------------------------------------------------------
219// GLOBAL_REQUEST (RFC 4254 §4; §7.1 tcpip-forward / cancel-tcpip-forward)
220// ---------------------------------------------------------------------------
221
222int ssh_global_request_handle(uint8_t i, const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len, size_t cap)
223{
224 *out_len = 0;
225 if (i >= MAX_SSH_CONNS || len < 1 || payload[0] != SSH_MSG_GLOBAL_REQUEST)
226 {
227 return -1;
228 }
229
230 size_t off = 1;
231 const uint8_t *name;
232 uint32_t name_len;
233 if (!rd_string(payload, len, &off, &name, &name_len))
234 {
235 return -1;
236 }
237 if (off >= len)
238 {
239 return -1;
240 }
241 bool want_reply = payload[off++] != 0;
242
243 bool is_fwd = (name_len == 13 && memcmp(name, "tcpip-forward", 13) == 0);
244 bool is_cancel = (name_len == 20 && memcmp(name, "cancel-tcpip-forward", 20) == 0);
245
246 if (is_fwd || is_cancel)
247 {
248 // Request-specific data: bind address (string) followed by bind port (uint32).
249 const uint8_t *addr;
250 uint32_t addr_len;
251 if (!rd_string(payload, len, &off, &addr, &addr_len) || off + 4 > len)
252 {
253 return -1;
254 }
255 uint16_t bind_port = (uint16_t)rd_u32(payload + off);
256
257 // The owner allocates (or cancels) the real listener; -1 means "refused".
258 int bound = -1;
259 if (is_fwd && s_chcb.rfwd_open_cb)
260 {
261 bound = s_chcb.rfwd_open_cb(i, (const char *)addr, addr_len, bind_port);
262 }
263 else if (is_cancel && s_chcb.rfwd_cancel_cb)
264 {
265 bound = s_chcb.rfwd_cancel_cb(i, (const char *)addr, addr_len, bind_port);
266 }
267
268 if (bound < 0)
269 {
270 if (want_reply) // refused: no owner, policy denied, or the table is full
271 {
272 if (cap < 1)
273 {
274 return -1;
275 }
277 *out_len = 1;
278 }
279 return 0;
280 }
281
282 if (want_reply)
283 {
284 // A tcpip-forward that requested port 0 echoes the allocated port
285 // (RFC 4254 §7.1); a specific port and cancel reply bare success.
286 if (is_fwd && bind_port == 0)
287 {
288 if (cap < 5)
289 {
290 return -1;
291 }
293 wr_u32(out + 1, (uint32_t)(uint16_t)bound);
294 *out_len = 5;
295 }
296 else
297 {
298 if (cap < 1)
299 {
300 return -1;
301 }
303 *out_len = 1;
304 }
305 }
306 return 0;
307 }
308
309 // Any other global request is unrecognized: RFC 4254 §4 -> REQUEST_FAILURE when the
310 // client wants a reply, otherwise silently ignored. (Never UNIMPLEMENTED: the
311 // GLOBAL_REQUEST message type is known; only this request name is not.)
312 if (want_reply)
313 {
314 if (cap < 1)
315 {
316 return -1;
317 }
319 *out_len = 1;
320 }
321 return 0;
322}
323
324// ---------------------------------------------------------------------------
325// Server-initiated CHANNEL_OPEN (forwarded-tcpip, ssh -R) + its CONFIRM / FAILURE
326// ---------------------------------------------------------------------------
327
328int pc_ssh_channel_open_forwarded(uint8_t i, const char *conn_addr, uint16_t conn_port, const char *orig_addr,
329 uint16_t orig_port, uint8_t *out, size_t *out_len, size_t cap)
330{
331 *out_len = 0;
332 if (i >= MAX_SSH_CONNS || !conn_addr || !orig_addr)
333 {
334 return -1;
335 }
336 int slot = chan_alloc(i);
337 if (slot < 0)
338 {
339 return -1; // channel pool full
340 }
341
342 const char *type = "forwarded-tcpip";
343 size_t tl = 15, ca = strnlen(conn_addr, cap), oa = strnlen(orig_addr, cap);
344 // byte || string(type) || u32 sender || u32 window || u32 maxpkt || string(conn_addr)
345 // || u32 conn_port || string(orig_addr) || u32 orig_port (RFC 4254 §7.2)
346 size_t need = 1 + (4 + tl) + 12 + (4 + ca) + 4 + (4 + oa) + 4;
347 if (cap < need)
348 {
349 return -1;
350 }
351
352 size_t off = 0;
353 out[off++] = SSH_MSG_CHANNEL_OPEN;
354 wr_u32(out + off, (uint32_t)tl);
355 memcpy(out + off + 4, type, tl);
356 off += 4 + tl;
357 wr_u32(out + off, (uint32_t)slot); // our sender channel id
358 wr_u32(out + off + 4, SSH_CHAN_WINDOW);
359 wr_u32(out + off + 8, SSH_CHAN_MAX_PACKET);
360 off += 12;
361 wr_u32(out + off, (uint32_t)ca);
362 memcpy(out + off + 4, conn_addr, ca);
363 off += 4 + ca;
364 wr_u32(out + off, conn_port);
365 off += 4;
366 wr_u32(out + off, (uint32_t)oa);
367 memcpy(out + off + 4, orig_addr, oa);
368 off += 4 + oa;
369 wr_u32(out + off, orig_port);
370 off += 4;
371
372 SshChannel *c = &ssh_chan[i][slot];
373 c->open = false;
374 c->pending = true; // awaiting the client's CHANNEL_OPEN_CONFIRMATION
376 c->local_id = (uint32_t)slot;
377 c->peer_id = 0;
379 c->peer_window = 0;
380 c->peer_max_pkt = 0;
381
382 *out_len = off;
383 return slot;
384}
385
386int pc_ssh_channel_handle_open_confirm(uint8_t i, const uint8_t *payload, size_t len)
387{
388 // byte || recipient(our local id) || sender(peer id) || window || max packet.
389 if (i >= MAX_SSH_CONNS || len < 17 || payload[0] != SSH_MSG_CHANNEL_OPEN_CONFIRM)
390 {
391 return -1;
392 }
393 SshChannel *c = chan_pending_by_id(i, rd_u32(payload + 1));
394 if (!c)
395 {
396 return -1;
397 }
398 c->peer_id = rd_u32(payload + 5);
399 c->peer_window = rd_u32(payload + 9);
400 c->peer_max_pkt = rd_u32(payload + 13);
401 c->pending = false;
402 c->open = true;
403 if (s_chcb.forward_confirm_cb)
404 {
405 s_chcb.forward_confirm_cb(i, c->local_id, true);
406 }
407 return 0;
408}
409
410int pc_ssh_channel_handle_open_failure(uint8_t i, const uint8_t *payload, size_t len)
411{
412 // byte || recipient(our local id) || reason || desc || lang.
413 if (i >= MAX_SSH_CONNS || len < 5 || payload[0] != SSH_MSG_CHANNEL_OPEN_FAILURE)
414 {
415 return -1;
416 }
417 SshChannel *c = chan_pending_by_id(i, rd_u32(payload + 1));
418 if (!c)
419 {
420 return -1;
421 }
422 uint32_t ch = c->local_id;
423 c->pending = false;
424 c->open = false; // free the slot; the client refused the forward
425 if (s_chcb.forward_confirm_cb)
426 {
427 s_chcb.forward_confirm_cb(i, ch, false);
428 }
429 return 0;
430}
431
432int pc_ssh_channel_handle_open(uint8_t i, const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len, size_t cap)
433{
434 if (i >= MAX_SSH_CONNS || len < 1 || payload[0] != SSH_MSG_CHANNEL_OPEN)
435 {
436 return -1;
437 }
438
439 size_t off = 1;
440 const uint8_t *type;
441 uint32_t type_len;
442 if (!rd_string(payload, len, &off, &type, &type_len))
443 {
444 return -1;
445 }
446 if (off + 12 > len)
447 {
448 return -1;
449 }
450 uint32_t sender = rd_u32(payload + off);
451 uint32_t init_window = rd_u32(payload + off + 4);
452 uint32_t max_pkt = rd_u32(payload + off + 8);
453 off += 12;
454
455 bool is_session = (type_len == 7 && memcmp(type, "session", 7) == 0);
456 bool is_dtcpip = (type_len == 12 && memcmp(type, "direct-tcpip", 12) == 0);
457 if (!is_session && !is_dtcpip)
458 {
459 return build_open_failure(out, cap, sender, 3u, out_len); // unknown channel type
460 }
461
462 // direct-tcpip data: host(string) port(u32) orig_host(string) orig_port(u32).
463 const uint8_t *fhost = nullptr;
464 uint32_t fhost_len = 0;
465 uint16_t fport = 0;
466 if (is_dtcpip)
467 {
468 if (!s_chcb.forward_open_cb)
469 {
470 return build_open_failure(out, cap, sender, 1u, out_len); // forwarding off: prohibited
471 }
472 if (!rd_string(payload, len, &off, &fhost, &fhost_len) || off + 4 > len)
473 {
474 return -1;
475 }
476 fport = (uint16_t)rd_u32(payload + off); // orig host/port follow but are advisory
477 }
478
479 int slot = chan_alloc(i);
480 if (slot < 0)
481 {
482 return build_open_failure(out, cap, sender, 4u, out_len); // pool full
483 }
484
485 SshChannel *c = &ssh_chan[i][slot];
486 c->open = true;
488 c->local_id = (uint32_t)slot;
489 c->peer_id = sender;
491 c->peer_window = init_window;
492 c->peer_max_pkt = max_pkt;
493
494 if (is_dtcpip)
495 {
496 // The owner does the actual TCP connect (no I/O in this codec); on refusal
497 // free the channel and fail closed.
498 if (s_chcb.forward_open_cb(i, c->local_id, (const char *)fhost, fhost_len, fport) < 0)
499 {
500 c->open = false;
501 return build_open_failure(out, cap, sender, 2u, out_len); // connect failed
502 }
503 }
504 return build_open_confirm(c, out, cap, out_len);
505}
506
507// ---------------------------------------------------------------------------
508// CHANNEL_REQUEST → SUCCESS / FAILURE
509// ---------------------------------------------------------------------------
510
511#if PC_ENABLE_SSH_SFTP || PC_ENABLE_SSH_SCP
512namespace
513{
514// A subsystem/exec CHANNEL_REQUEST may name a file-transfer service (SFTP or SCP). Tag @p c and fire the
515// matching open callback when it does; @p off points at the request-specific arg and may be advanced. Flips
516// *accept true for an accepted SFTP subsystem (exec is already in the base accept set).
517void classify_file_transfer_request(uint8_t i, SshChannel *c, const uint8_t *rtype, uint32_t rtype_len,
518 const uint8_t *payload, size_t len, size_t *off, bool *accept)
519{
520#if !PC_ENABLE_SSH_SFTP
521 (void)accept; // only the SFTP subsystem path flips acceptance; scp exec is already accepted
522#endif
523#if PC_ENABLE_SSH_SFTP
524 // subsystem "sftp": not in the base accept set, so accept it here and tag the channel for the SFTP binding.
525 if (rtype_len == 9 && memcmp(rtype, "subsystem", 9) == 0)
526 {
527 const uint8_t *arg = nullptr;
528 uint32_t arg_len = 0;
529 if (rd_string(payload, len, off, &arg, &arg_len) && arg_len == 4 && memcmp(arg, "sftp", 4) == 0)
530 {
531 *accept = true;
533 if (s_chcb.pc_sftp_open_cb)
534 {
535 s_chcb.pc_sftp_open_cb(i, c->local_id);
536 }
537 }
538 }
539#endif
540#if PC_ENABLE_SSH_SCP
541 // exec "scp …": already accepted (exec is in the base set); tag the channel + hand the command to the binding.
542 if (rtype_len == 4 && memcmp(rtype, "exec", 4) == 0)
543 {
544 const uint8_t *arg = nullptr;
545 uint32_t arg_len = 0;
546 if (rd_string(payload, len, off, &arg, &arg_len) && arg_len >= 4 && memcmp(arg, "scp ", 4) == 0)
547 {
549 if (s_chcb.pc_scp_open_cb)
550 {
551 s_chcb.pc_scp_open_cb(i, c->local_id, (const char *)arg, arg_len);
552 }
553 }
554 }
555#endif
556}
557} // namespace
558#endif
559
560int pc_ssh_channel_handle_request(uint8_t i, const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len,
561 size_t cap)
562{
563 *out_len = 0;
564 if (i >= MAX_SSH_CONNS || len < 1 || payload[0] != SSH_MSG_CHANNEL_REQUEST)
565 {
566 return -1;
567 }
568
569 size_t off = 1;
570 if (off + 4 > len)
571 {
572 return -1;
573 }
574 uint32_t recipient = rd_u32(payload + off); // our channel id
575 off += 4;
576 const uint8_t *rtype;
577 uint32_t rtype_len;
578 if (!rd_string(payload, len, &off, &rtype, &rtype_len))
579 {
580 return -1;
581 }
582 if (off >= len)
583 {
584 return -1;
585 }
586 bool want_reply = payload[off++] != 0;
587
588 SshChannel *c = chan_by_id(i, recipient);
589 if (!c)
590 {
591 return -1;
592 }
593
594 bool accept =
595 (rtype_len == 5 && memcmp(rtype, "shell", 5) == 0) || (rtype_len == 4 && memcmp(rtype, "exec", 4) == 0) ||
596 (rtype_len == 7 && memcmp(rtype, "pty-req", 7) == 0) || (rtype_len == 3 && memcmp(rtype, "env", 3) == 0);
597
598#if PC_ENABLE_SSH_SFTP || PC_ENABLE_SSH_SCP
599 classify_file_transfer_request(i, c, rtype, rtype_len, payload, len, &off, &accept);
600#endif
601
602 if (!want_reply)
603 {
604 return 0;
605 }
606
607 if (cap < 5)
608 {
609 return -1;
610 }
612 wr_u32(out + 1, c->peer_id);
613 *out_len = 5;
614 return 0;
615}
616
617// ---------------------------------------------------------------------------
618// CHANNEL_DATA (inbound) + flow control
619// ---------------------------------------------------------------------------
620
621int pc_ssh_channel_handle_data(uint8_t i, const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len, size_t cap)
622{
623 *out_len = 0;
624 if (i >= MAX_SSH_CONNS || len < 1 || payload[0] != SSH_MSG_CHANNEL_DATA)
625 {
626 return -1;
627 }
628
629 size_t off = 1;
630 if (off + 4 > len)
631 {
632 return -1;
633 }
634 uint32_t recipient = rd_u32(payload + off);
635 off += 4;
636 const uint8_t *data;
637 uint32_t dlen;
638 if (!rd_string(payload, len, &off, &data, &dlen))
639 {
640 return -1;
641 }
642
643 SshChannel *c = chan_by_id(i, recipient);
644 if (!c)
645 {
646 return -1;
647 }
648 if (dlen > c->local_window)
649 {
650 return -1; // peer overran the advertised window (RFC 4254 §5.2)
651 }
652 c->local_window -= dlen;
653
654 if (dlen > 0)
655 {
656 switch (c->type)
657 {
659 case SshChanType::SSH_CHAN_FORWARDED_TCPIP: // forwarded TCP bytes (ssh -L / -R) -> the forward owner
660 if (s_chcb.forward_data_cb)
661 {
662 s_chcb.forward_data_cb(i, c->local_id, data, dlen);
663 }
664 break;
665#if PC_ENABLE_SSH_SFTP
666 case SshChanType::SSH_CHAN_SFTP: // SSH_FXP_* bytes -> the SFTP binding
667 if (s_chcb.pc_sftp_data_cb)
668 {
669 s_chcb.pc_sftp_data_cb(i, c->local_id, data, dlen);
670 }
671 break;
672#endif
673#if PC_ENABLE_SSH_SCP
674 case SshChanType::SSH_CHAN_SCP: // RCP protocol bytes -> the SCP binding
675 if (s_chcb.pc_scp_data_cb)
676 {
677 s_chcb.pc_scp_data_cb(i, c->local_id, data, dlen);
678 }
679 break;
680#endif
681 default: // SSH_CHAN_SESSION: shell/exec bytes -> the application
682 if (s_chcb.data_cb)
683 {
684 s_chcb.data_cb(i, c->local_id, data, dlen);
685 }
686 break;
687 }
688 }
689
690 // Replenish the window once it drops below half.
691 if (c->local_window < SSH_CHAN_WINDOW / 2)
692 {
693 uint32_t add = SSH_CHAN_WINDOW - c->local_window;
694 if (cap >= 9)
695 {
697 wr_u32(out + 1, c->peer_id);
698 wr_u32(out + 5, add);
699 *out_len = 9;
700 c->local_window += add;
701 }
702 }
703 return 0;
704}
705
706// ---------------------------------------------------------------------------
707// CHANNEL_DATA (outbound)
708// ---------------------------------------------------------------------------
709
710int pc_ssh_channel_build_data(uint8_t i, uint32_t channel, const uint8_t *data, size_t len, uint8_t *out,
711 size_t *out_len, size_t cap)
712{
713 SshChannel *c = (i < MAX_SSH_CONNS) ? chan_by_id(i, channel) : nullptr;
714 if (!c)
715 {
716 return -1;
717 }
718 if (len > c->peer_window || len > c->peer_max_pkt)
719 {
720 return -1; // would exceed the client's window / packet size
721 }
722 if (cap < 1 + 4 + 4 + len)
723 {
724 return -1;
725 }
726
727 out[0] = SSH_MSG_CHANNEL_DATA;
728 wr_u32(out + 1, c->peer_id);
729 wr_u32(out + 5, (uint32_t)len);
730 memcpy(out + 9, data, len);
731 *out_len = 9 + len;
732 c->peer_window -= (uint32_t)len;
733 return 0;
734}
735
736// ---------------------------------------------------------------------------
737// WINDOW_ADJUST (inbound)
738// ---------------------------------------------------------------------------
739
740int pc_ssh_channel_handle_window_adjust(uint8_t i, const uint8_t *payload, size_t len)
741{
742 if (i >= MAX_SSH_CONNS || len < 9 || payload[0] != SSH_MSG_CHANNEL_WINDOW_ADJUST)
743 {
744 return -1;
745 }
746 SshChannel *c = chan_by_id(i, rd_u32(payload + 1));
747 if (!c)
748 {
749 return -1;
750 }
751 uint32_t add = rd_u32(payload + 5);
752 // Saturate rather than overflow the 32-bit window.
753 uint32_t w = c->peer_window;
754 c->peer_window = (w + add < w) ? 0xFFFFFFFFu : (w + add);
755 return 0;
756}
757
758// ---------------------------------------------------------------------------
759// EOF + CLOSE
760// ---------------------------------------------------------------------------
761
762// Frame EOF + CLOSE for an open channel and mark it closed (shared by the inbound
763// handler and the app/teardown path).
764static int build_close_chan(SshChannel *c, uint8_t *out, size_t *out_len, size_t cap)
765{
766 if (!c || cap < 10)
767 {
768 return -1;
769 }
770 uint32_t peer = c->peer_id;
771 out[0] = SSH_MSG_CHANNEL_EOF;
772 wr_u32(out + 1, peer);
773 out[5] = SSH_MSG_CHANNEL_CLOSE;
774 wr_u32(out + 6, peer);
775 *out_len = 10;
776 c->open = false;
777 return 0;
778}
779
780int pc_ssh_channel_build_close(uint8_t i, uint32_t channel, uint8_t *out, size_t *out_len, size_t cap)
781{
782 if (i >= MAX_SSH_CONNS)
783 {
784 return -1;
785 }
786 return build_close_chan(chan_by_id(i, channel), out, out_len, cap);
787}
788
789int pc_ssh_channel_handle_close(uint8_t i, const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len,
790 size_t cap)
791{
792 *out_len = 0;
793 if (i >= MAX_SSH_CONNS || len < 5 || payload[0] != SSH_MSG_CHANNEL_CLOSE)
794 {
795 return -1;
796 }
797 return build_close_chan(chan_by_id(i, rd_u32(payload + 1)), out, out_len, cap);
798}
#define MAX_SSH_CONNS
Definition c2_defaults.h:85
#define PC_SSH_MAX_CHANNELS
Definition c2_defaults.h:88
#define SSH_CHAN_WINDOW
Initial receive window the SSH server advertises (RFC 4254 §5.1).
#define SSH_CHAN_MAX_PACKET
Maximum SSH channel data payload the server advertises it can receive per message.
int pc_ssh_channel_build_close(uint8_t i, uint32_t channel, uint8_t *out, size_t *out_len, size_t cap)
Build SSH_MSG_CHANNEL_EOF + SSH_MSG_CHANNEL_CLOSE for channel channel and mark it closed.
int pc_ssh_channel_handle_window_adjust(uint8_t i, const uint8_t *payload, size_t len)
Handle SSH_MSG_CHANNEL_WINDOW_ADJUST (grows the peer window).
int pc_ssh_channel_open_forwarded(uint8_t i, const char *conn_addr, uint16_t conn_port, const char *orig_addr, uint16_t orig_port, uint8_t *out, size_t *out_len, size_t cap)
Open a server-initiated "forwarded-tcpip" channel (RFC 4254 §7.2, ssh -R).
int pc_ssh_channel_handle_close(uint8_t i, const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len, size_t cap)
Handle an inbound SSH_MSG_CHANNEL_CLOSE: route to the recipient channel, reply with EOF + CLOSE,...
int pc_ssh_channel_handle_open(uint8_t i, const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len, size_t cap)
Handle SSH_MSG_CHANNEL_OPEN and emit CHANNEL_OPEN_CONFIRMATION.
int pc_ssh_channel_handle_data(uint8_t i, const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len, size_t cap)
Handle SSH_MSG_CHANNEL_DATA: bounds-check, update the window, and invoke the data callback....
void pc_ssh_channel_set_forward_data_cb(SshForwardDataCb cb)
Install the direct-tcpip forward inbound-data callback.
int ssh_global_request_handle(uint8_t i, const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len, size_t cap)
Handle SSH_MSG_GLOBAL_REQUEST (RFC 4254 §4).
void pc_ssh_channel_init(uint8_t i)
Reset channel state for slot i.
void pc_ssh_channel_set_data_cb(SshChannelDataCb cb)
Install the inbound-data callback (session channels).
void pc_ssh_channel_set_forward_confirm_cb(SshForwardConfirmCb cb)
Install the forwarded-tcpip open-confirmation callback (opt-in, ssh -R).
void pc_ssh_channel_set_rforward_open_cb(SshRemoteForwardOpenCb cb)
Install the remote-forward (ssh -R) open-policy callback (opt-in).
int pc_ssh_channel_build_data(uint8_t i, uint32_t channel, const uint8_t *data, size_t len, uint8_t *out, size_t *out_len, size_t cap)
Build an SSH_MSG_CHANNEL_DATA message carrying data to the client on channel channel (a local channel...
void pc_ssh_channel_set_rforward_cancel_cb(SshRemoteForwardCancelCb cb)
Install the remote-forward (ssh -R) cancel callback (opt-in).
int pc_ssh_channel_handle_open_confirm(uint8_t i, const uint8_t *payload, size_t len)
Handle SSH_MSG_CHANNEL_OPEN_CONFIRMATION for a channel we opened (ssh -R).
SshChannel ssh_chan[MAX_SSH_CONNS][PC_SSH_MAX_CHANNELS]
Channel pool: PC_SSH_MAX_CHANNELS channels per SSH connection (BSS). Owned by this layer; src/ code r...
int pc_ssh_channel_handle_open_failure(uint8_t i, const uint8_t *payload, size_t len)
Handle SSH_MSG_CHANNEL_OPEN_FAILURE for a channel we opened (ssh -R).
int pc_ssh_channel_handle_request(uint8_t i, const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len, size_t cap)
Handle SSH_MSG_CHANNEL_REQUEST.
void pc_ssh_channel_set_forward_open_cb(SshForwardOpenCb cb)
Install the direct-tcpip forward open-policy callback (opt-in).
SSH connection protocol - multiplexed "session" channels (RFC 4254).
int(*)(uint8_t slot, uint32_t channel, const char *host, size_t host_len, uint16_t port) SshForwardOpenCb
"direct-tcpip" forward request: a client asked the server to open a TCP connection to host : port (ss...
Definition ssh_channel.h:77
int(*)(uint8_t slot, const char *bind_addr, size_t addr_len, uint16_t bind_port) SshRemoteForwardOpenCb
"tcpip-forward" remote-forward request (ssh -R): the client asks the server to listen on bind_addr : ...
Definition ssh_channel.h:99
@ SSH_CHAN_DIRECT_TCPIP
"direct-tcpip" - client-initiated TCP forward (ssh -L)
@ SSH_CHAN_SESSION
"session" - shell / exec / data
@ SSH_CHAN_FORWARDED_TCPIP
"forwarded-tcpip" - server-initiated TCP forward (ssh -R)
@ SSH_CHAN_SCP
a "session" running an exec "scp …" (PC_ENABLE_SSH_SCP)
@ SSH_CHAN_SFTP
a "session" running the "sftp" subsystem (PC_ENABLE_SSH_SFTP)
int(*)(uint8_t slot, const char *bind_addr, size_t addr_len, uint16_t bind_port) SshRemoteForwardCancelCb
"cancel-tcpip-forward" request (RFC 4254 §7.1): drop a remote forward.
void(*)(uint8_t slot, uint32_t channel, const uint8_t *data, size_t len) SshForwardDataCb
Inbound data on a direct-tcpip channel (the owner writes it to the forwarded TCP socket)....
Definition ssh_channel.h:81
void(*)(uint8_t slot, uint32_t channel, bool ok) SshForwardConfirmCb
Result of the client's reply to a server-initiated forwarded-tcpip channel: ok = true on CHANNEL_OPEN...
void(*)(uint8_t slot, uint32_t channel, const uint8_t *data, size_t len) SshChannelDataCb
Application callback for inbound channel data (raw bytes), tagged with the channel id it arrived on.
Definition ssh_channel.h:61
SSH binary packet protocol: framing, AES-256-CTR encryption, HMAC-SHA2-256 integrity,...
#define SSH_MSG_CHANNEL_SUCCESS
Definition ssh_packet.h:125
#define SSH_MSG_CHANNEL_EOF
Definition ssh_packet.h:122
#define SSH_MSG_CHANNEL_DATA
Definition ssh_packet.h:121
#define SSH_MSG_CHANNEL_FAILURE
Definition ssh_packet.h:126
#define SSH_MSG_CHANNEL_OPEN_CONFIRM
Definition ssh_packet.h:118
#define SSH_MSG_CHANNEL_OPEN
Definition ssh_packet.h:117
#define SSH_MSG_CHANNEL_REQUEST
Definition ssh_packet.h:124
#define SSH_MSG_CHANNEL_OPEN_FAILURE
Definition ssh_packet.h:119
#define SSH_MSG_REQUEST_SUCCESS
Definition ssh_packet.h:115
#define SSH_MSG_CHANNEL_CLOSE
Definition ssh_packet.h:123
#define SSH_MSG_REQUEST_FAILURE
Definition ssh_packet.h:116
#define SSH_MSG_GLOBAL_REQUEST
Definition ssh_packet.h:114
#define SSH_MSG_CHANNEL_WINDOW_ADJUST
Definition ssh_packet.h:120
SshForwardOpenCb forward_open_cb
SshRemoteForwardCancelCb rfwd_cancel_cb
SshForwardConfirmCb forward_confirm_cb
SshChannelDataCb data_cb
SshForwardDataCb forward_data_cb
SshRemoteForwardOpenCb rfwd_open_cb
Per-connection channel state.
Definition ssh_channel.h:43
bool open
True once the channel is confirmed open both ways.
Definition ssh_channel.h:44
uint32_t peer_max_pkt
Client's maximum packet size.
Definition ssh_channel.h:51
uint32_t peer_window
Bytes we may still send to the client.
Definition ssh_channel.h:50
SshChanType type
session, direct-tcpip, or forwarded-tcpip.
Definition ssh_channel.h:46
bool pending
True for a server-initiated channel we opened, awaiting the client's confirmation.
Definition ssh_channel.h:45
uint32_t local_id
Our channel id (== slot index).
Definition ssh_channel.h:47
uint32_t local_window
Bytes we may still receive before WINDOW_ADJUST.
Definition ssh_channel.h:49
uint32_t peer_id
Client's channel id.
Definition ssh_channel.h:48