Ciro Iriarte created GUACAMOLE-2307:
---------------------------------------

             Summary: Add opt-in raw text-output mode for terminal protocols 
(SSH/telnet/Kubernetes)
                 Key: GUACAMOLE-2307
                 URL: https://issues.apache.org/jira/browse/GUACAMOLE-2307
             Project: Guacamole
          Issue Type: New Feature
          Components: guacamole-server
            Reporter: Ciro Iriarte


h2. Summary

Add an opt-in per-connection parameter, {{text-output}}, that makes guacd tee 
the
raw remote PTY byte stream (including ANSI/CSI/OSC sequences) to an outbound
Guacamole {{pipe}} stream named {{STDOUT}}, for the SSH, telnet, and Kubernetes
protocols. This lets a non-graphical / native CLI client present a terminal 
session
as true in-terminal text instead of decoding rasterized glyphs. Default off; no
change to existing connections.

h2. Motivation

The browser terminal renders output graphically: guacd runs the emulator and 
emits
only drawing instructions, so no character codepoints reach the client. A 
native or
CLI client therefore cannot reconstruct the text of a session. This adds a 
faithful
raw byte channel in the output direction, complementing the inbound STDIN pipe
(GUACAMOLE-574). It deliberately does *not* reuse the {{guacctl}} pipe redirect
(GUACAMOLE-17): that path runs through {{guac_terminal_echo()}}, which strips
ESC/CSI/OSC sequences and so cannot carry a faithful raw stream.

h2. Design

* New connection parameter {{text-output}} (string enum):
** {{true}} — tee mode: raw bytes are teed to STDOUT *and* the graphical 
display is
   still rendered, so browser users are unaffected.
** {{raw}} — headless mode: the graphical terminal is not rendered at all; 
bytes are
   delivered only via STDOUT, eliminating the graphical instruction stream and 
its
   render cost.
** anything else, including {{false}} or omitted — disabled.
* guacd opens an outbound {{pipe}} stream named {{STDOUT}}, mimetype
  {{application/octet-stream}} (advisory; payload is raw bytes carried in base64
  {{blob}} instructions, so it is binary-safe and charset-agnostic — the client
  decodes the bytes itself).
* The tee is taken *at the protocol source* — in each protocol's PTY read path,
  upstream of the terminal emulator — via a dedicated {{text_output_stream}} on
  {{guac_terminal}}, independent of the {{guacctl}} pipe machinery.
* The stream is allocated on the connection *owner's* user socket
  ({{guac_user_alloc_stream}}), not broadcast to every user, and uses an even
  (ack-routable) stream index so client {{ack}} instructions return to the 
stream.
* New additive public API in {{terminal.h}}:
  {{guac_terminal_text_output_open/write/flush/close}} (+ a {{should_open}} 
helper).
  No existing symbol is changed.
* In {{raw}} mode the read loop skips {{guac_terminal_write()}} (the terminal 
is kept
  for input/size/stdin) and flushes per-read, since there is no graphical frame 
cycle.

h2. Flow control / backpressure

This is the subtle part and is worth reviewer attention:

* Clients must {{ack}} every {{blob}}, on receipt rather than after rendering.
* guacd bounds the unacknowledged backlog by *bytes*: 256 KB
  ({{GUAC_TERMINAL_TEXT_OUTPUT_MAX_INFLIGHT_BYTES}}), plus a 256-blob cap 
purely to
  bound a small size-tracking FIFO. The byte bound is the operative one, because
  {{raw}} mode emits one blob per PTY read and those blobs are often only a few 
bytes.
* On a stalled consumer the response differs by mode, deliberately:
** {{tee}}: buffered output is *dropped* and the session continues. The tee 
shares the
   protocol read loop with the graphical display, so blocking on a stalled text
   consumer would also stall any co-attached browser user.
** {{raw}}: the read loop is *throttled* — the writer waits (on a condition 
variable)
   for the window to drain, which propagates backpressure to the remote program
   through the PTY exactly as a slow local terminal would. Raw renders nothing
   graphically, so pausing starves no one and the byte stream stays intact.
* A consumer that stops acking entirely for 15 s
  ({{GUAC_TERMINAL_TEXT_OUTPUT_STALL_TIMEOUT}}) is disconnected with 
{{SERVER_ERROR}}.
  The wait also short-circuits immediately once the connection is going away, 
so an
  abrupt disconnect does not delay teardown.

h2. Security

* {{text-output}} is effectively a copy/export channel, so it is gated behind
  {{disable-copy}}: guacd refuses to open the pipe when copying from the 
terminal is
  disabled (and logs a warning).
* Pipe contents are not logged or recorded by default (output may echo secrets).
* Intended trust model: the parameter is admin-provisioned on the connection 
(not
  client-requestable); guacd is reached only over the authenticated 
tunnel/gateway.
  No new port, no new protocol instruction (reuses 
{{pipe}}/{{blob}}/{{end}}/{{ack}}),
  no authentication change.

h2. Backwards compatibility / ABI

Additive and opt-in, default off. No new protocol instruction, no DB schema 
change,
and no web-app change is required — an unmodified guacamole-client already 
passes the
parameter through, since {{ConfiguredGuacamoleSocket}} builds the {{connect}}
instruction from the argument list guacd announces in its handshake. No ABI 
break:
only additive public symbols are introduced.

h2. Validation

* CUnit unit tests for terminal text-output: disable-copy gating, owner-stream 
+ ack
  routing, tee drop-on-stall, raw throttle-vs-abort, the byte bound, ACK 
retiring the
  oldest blob's byte count, surplus-ACK safety, disconnect short-circuit, and
  closed-stream-unroutable. Full {{make check}} is green on Debian 12 and 
openSUSE
  Leap 16.
* An end-to-end harness (direct-to-guacd, no web app) covers 
SSH/telnet/Kubernetes
  across 13 scenarios: positive tee, negative (default-off and disable-copy -> 
no
  pipe), a byte-bound flood with and without acks, small-unacked-not-dropped, 
and
  raw (text delivered, graphics suppressed). All pass.
* Validated live through an unmodified guacamole-client gateway with a native 
CLI
  client, in both tee and raw modes, including a 20,000-line flood and the 15 s
  give-up path (measured: 256 KB delivered, then a clean SERVER_ERROR abort).

h2. Patch / branch

Implemented on a fork branch, 23 commits atop apache/main (base 6719b20d). A
redistributable {{git format-patch}} artifact is available and applies cleanly 
onto
that base. Happy to open a PR against apache/main. The client side is UI-only
convenience (protocol JSON enum + i18n strings), and a matching manual section
documents the parameter and the backpressure contract.

h2. Related

* GUACAMOLE-17 (resolved) — the original {{guacctl}} output-to-pipe redirect; 
routes
  through the escape-stripping echo path, so it is not usable for a faithful raw
  stream. This issue is the complement, taken at the source.
* GUACAMOLE-574 — inbound STDIN pipe (the input direction).



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to