CI hadn't run on macOS or Windows for a while (billing); once it did:
- Windows: the RDP shared-drive code and its symlink test are Unix-only
but were compiled there (and the unused pieces failed clippy). The drive
type, its impls, imports and test are now cfg(unix); refusing a drive
request is a free function used everywhere. Other Windows-only warnings
fixed (serial test import, unused mut, harden_process).
- macOS: the local shell's directory was sampled on the first output
after Enter, which is the shell echoing the command before a cd runs,
so restored tabs opened one directory behind. It's now sampled shortly
after Enter and again once the command has had time to finish.
- packaging/repo.sh publishes the 'nexusterminal' .deb/.rpm/Arch packages
to repo.dixon.cx from the release's own Linux binaries, verified
against SHA256SUMS.
- The desktop entry mentions RDP.
- LICENSE: the GNU Affero General Public License, version 3 (verbatim
from gnu.org).
- SPDX-License-Identifier: AGPL-3.0-only plus a copyright line on every
first-party source file (Rust, shell, Python, PHP); vendored crates keep
their own licenses.
- Cargo manifests carry license/authors/repository; the macOS bundle
copyright and the About page state the license and link the source.
- Release zips (and the macOS app bundle) include LICENSE.
- README: License section.
- The local clipboard was offered to the RDP server every time the
desktop gained focus, so any server could read the last thing copied
(a password, a token) without a paste. It now goes only on Ctrl/Cmd+V
or Paste Clipboard.
- The server-chosen desktop size is checked before DecodedImage
allocates it (65535x65535 is 16 GiB, and allocation failure aborts the
whole app), at connect and on reactivation.
- Drive reads are clamped to 8 MiB per request (the native backend
allocated the server's u32 length up front).
- Print jobs get unique files created exclusively (same-second jobs used
to truncate each other) and at most 4 may be open at once.
An RDP profile can name a Kerberos KDC; CredSSP then authenticates with
Kerberos instead of NTLM, reaching the KDC through a small TCP client
(RFC 4120 length-prefixed framing, 5 s connect / 10 s read timeouts,
replies capped at 1 MiB so a hostile KDC can't force a huge allocation).
UDP KDCs are refused with a clear message. Tested against a fake KDC
(round trip, oversized reply, UDP); not against a real realm.
Smart-card logon is not implemented: it needs PC/SC hardware and a card
to build and test against.
RDP profiles can go through a Remote Desktop Gateway (MS-TSGU over an
HTTPS WebSocket) with its own username and password, stored like other
credentials (rdp-gateway-password). The gateway stream is bridged to a
loopback socket so the blocking RDP client uses it like any transport.
IronRDP's gateway crate (0.0.1) is vendored with fixes: it used
ironrdp-tls, which accepts any certificate, before sending Basic
credentials — the gateway is now verified against the web PKI; its
asserts on gateway-supplied lengths are errors instead of panics; and
the server port is passed through. Checked against a self-signed
openssl s_server: the connection is refused ('invalid peer certificate')
before any request or credential is sent. Not tested against a real
gateway; Basic-auth gateways only (no NTLM).
An optional 'Nexus Terminal' printer (IronRDP's PostScript driver
announce) appears on the remote desktop; each job is written to
'Nexus print <time>.ps' in Downloads, with a toast saying where. Jobs
are capped at 256 MiB and empty jobs leave no file. The shared drive and
the printer now share one RDPDR backend (rdp_devices); the printer
works on every platform, the drive on Linux and macOS.
Tested with a unit test driving create/write/close; xrdp has no printer
redirection, so not exercised against a server.
- Audio: the RDPSND channel with IronRDP's cpal playback backend (PCM;
Opus left out to avoid another native library). On by default for new
RDP profiles. Building now needs the ALSA headers on Linux
(libasound2-dev), added to CI, release and fuzz jobs and the build docs.
- Shared folder (Linux/macOS): one local folder appears on the remote
desktop as a drive. IronRDP's native backend joins server paths onto
the share unchecked, so it is wrapped in ConfinedDrive: create, list
and rename paths with '..' components, NUL bytes, or that resolve
through a symlink outside the share are refused (ACCESS_DENIED);
tests cover the escapes.
Verified against xrdp (with FUSE): the remote read the shared file, and
writes, mkdir and rename landed in the local folder. Not verified:
audio playback (the test container has no sound module), and xrdp lists
the drive as 'ignored' and can't list its directories (an IronRDP/xrdp
naming and listing mismatch, fixed upstream for the name).
A CLIPRDR channel with a text-only backend (CF_UNICODETEXT): text copied
on the remote desktop is fetched and goes through the app's remote-
clipboard preference like VNC and OSC 52; local text is offered to the
remote when the desktop gains focus, on Ctrl+V and with Paste Clipboard
(which no longer types the text). Backend callbacks run inside the
active stage, so they queue requests that the session loop sends.
Verified against xrdp: the remote X clipboard read back the offered
text, and a remote copy arrived through on_clipboard.
- Credential callbacks (SecretFn) now return Zeroizing<String>, as do the
session-only/pending caches they read and the RDP client's password,
so Nexus's own copies are wiped; a plain String copy is made only
where a library API needs one (SSH password auth, IRC, VNC, CredSSP).
- Release builds disable core dumps (RLIMIT_CORE 0) and, on Linux, mark
the process non-dumpable (PR_SET_DUMPABLE 0), which also blocks ptrace
attach and /proc/<pid>/mem reads by other processes of the same user.
Debug builds are unchanged so they stay debuggable. Verified on the
release binary: core limit 0, /proc/<pid>/{environ,mem} root-owned and
unreadable, local shells (including cwd capture) still work.
Closes the remaining parts of SEC-20261012-007 that the app controls;
egui's internal text buffers remain outside its reach.
Every client's reader thread (Telnet/RAW, serial, local shell, IRC, VNC,
RDP) and SSH's channel reader task now run under clients::guarded /
guarded_task. A panic there, such as a decoder bug triggered by a
hostile server, reports 'internal error: …' through the session's
on_error and then closes it, so the tab shows the error (and
auto-reconnect can run) instead of looking connected with nothing
reading. Other sessions were already unaffected since the switch to
parking_lot locks.
- Protocol::Rdp with RdpConfig (domain, password mode, certificate
policy, TLS-only fallback, fixed or tab-sized resolution, SOCKS5/HTTP
proxy, SSH tunnel); migration 008 adds rdp_config and drops the
protocol CHECK constraint so new protocols no longer need a rebuild.
- Session manager: connect_rdp dials directly, via the shared proxy
module (proxy password from the secret store) or an SSH tunnel; the
server certificate goes through the known-hosts check (key type
rdp-tls); a tab-sized desktop uses the tab's reported pixel size, and
resizes wait until the server opens Display Control. Rejected NLA
passwords count as auth failures, so a saved one is asked again.
- The VNC viewer becomes DesktopView for both protocols; a small adapter
turns its keysyms and pointer masks into RDP scancodes, Unicode keys,
buttons and wheel events. For RDP, Paste Clipboard types the text.
- Profile editor RDP section, Quick Connect, badges, docs.
Verified against xrdp in the app: logon, desktop, input, and connections
through SOCKS5 and HTTP CONNECT relays. Not verified here: Windows/NLA
servers, the SSH tunnel route, and Display Control resizing (xrdp
0.9.24 never opened the channel).
clients::rdp connects over any caller-dialled TCP stream (direct, proxy
or SSH tunnel), upgrades to TLS and logs on with NLA (CredSSP/NTLM),
optionally falling back to TLS-only logon for servers without NLA such
as xrdp (credentials are passed so they log straight in). The server
certificate is pinned through a caller check on its SHA256 fingerprint
before any credentials are sent. The desktop decodes into the VNC
client's RGBA framebuffer; input (IronRDP's scancode/Unicode/mouse
operations), Display Control resizes and the deactivation-reactivation
sequence run on the session thread.
IronRDP's releases need three vendored patches (vendor/README.md):
picky and sspi with their RustCrypto release-candidate pins relaxed so
they share russh's stable versions (their own suites, 402 tests, pass
against them), and ironrdp-session with two unreleased upstream bitmap
fixes (#1486 stride, #1524 RDP6 orientation) without which xrdp desktops
render sheared.
Verified live against xrdp (scottyhardy/docker-remote-desktop): logon,
full desktop rendering, keyboard and mouse input.