Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement semantically correct handling of shell/pty sessions across platforms #15

Merged
merged 2 commits into from
Aug 7, 2022

Conversation

mohammed90
Copy link
Collaborator

@mohammed90 mohammed90 commented Jun 29, 2022

This PR resolves 3 issues:

  1. The issue was recognized when a friend reported scp and rsync not working. I found the culprit to be not hooking up the session's/channel's I/O to spawned process, otherwise the new process assumed the null devices as its std{in,out,err}. In other words, the newly created process was not reading/writing to/from the client's shell, rather from, e.g., secondary tty device (pair of the pty device) 1. Thus the I/O was not channeled from the client to the process, rather to the PTY session. The PR hooks the client's I/O directly as the newly spawned process' std{in,out}. This means when issuing a command which expects to read bytes from stdin, the stdin of the remote process is fed from the local client, not the remote tty. Now scp et. al. can work.

  2. If you're on macOS, go look for your user in /etc/passwd. You will not find it. macOS uses Open Directory2 to manage the users, so the users' {meta,}data are stored in the DirectoryService(8) database. In Go, implementation of user lookup in os/user pkg uses the native function getpwnam_r with cgo; but only parses /etc/passwd otherwise, except for lookups of current user which goes through syscall. Lookups return empty results for other users (see os/user: LookupUser() doesn't find users on macOS when compiled with CGO_ENABLED=0 golang/go#24383). To avoid cgo and still maintain robust and comprehensive offering, I had to shell out to dscacheutil on macOS to obtain full user details and parse /etc/passwd on other *nix platforms.

  3. Change the Shell field in the Shell struct to ForceCommand, and use the opportunity to line-up the semantics to match OpenSSH behavior. The ForceCommand, if set, is executed using the user's shell, regardless of the command sent along the connection. The designated shell is always the user's default shell, which on *nix systems typically found in /etc/passwd and $SHELL.

TODO:

  • update README examples

Footnotes

  1. https://dev.to/napicella/linux-terminals-tty-pty-and-shell-192e

  2. https://en.wikipedia.org/wiki/Apple_Open_Directory

@mohammed90 mohammed90 force-pushed the enhance-shell branch 5 times, most recently from 194579f to a4ed3f1 Compare June 30, 2022 00:07
…cross platforms

The `shell` module now correctly handles `scp` and similar commands. This commit resolves 3 inter-related matters:

1. The issue was recognized when a friend reported `scp` and `rsync` not working. I found the culprit to be not hooking up the session's/channel's I/O to spawned process, otherwise the new process assumed the null devices as its std{in,out,err}. In other words, the newly created process was not reading/writing to/from the client's shell, rather from, e.g., secondary tty device (pair of the pty device) [^1]. Thus the I/O was not channeled from the client to the process, rather to the PTY session. The PR hooks the client's I/O directly as the newly spawned process' std{in,out}. This means when issuing a command which expects to read bytes from stdin, the stdin of the remote process is fed from the local client, not the remote tty. Now `scp` et. al. can work.

2. If you're on macOS, go look for your user in `/etc/passwd`. You will not find it. macOS uses Open Directory[^2] to manage the users, so the users' {meta,}data are stored in the DirectoryService(8) database. In Go, implementation of user lookup in `os/user` pkg uses the native function `getpwnam_r` with cgo; but only parses `/etc/passwd` otherwise, except for lookups of current user which goes through syscall. Lookups return empty results for other users (see golang/go#24383). To avoid cgo and still maintain robust and comprehensive offering, I had to shell out to `dscacheutil` on macOS to obtain full user details and parse /etc/passwd on other *nix platforms.

3. Change the `Shell` field in the `Shell` struct to `ForceCommand`, and use the opportunity to line-up the semantics to match OpenSSH behavior. The `ForceCommand`, if set, is executed using the user's shell, regardless of the command sent along the connection. The designated shell is always the user's default shell, which on *nix systems typically found in `/etc/passwd` and `$SHELL`.

[^1]: https://dev.to/napicella/linux-terminals-tty-pty-and-shell-192e
[^2]: https://en.wikipedia.org/wiki/Apple_Open_Directory
@mohammed90 mohammed90 merged commit ac1b811 into master Aug 7, 2022
@mohammed90 mohammed90 deleted the enhance-shell branch August 7, 2022 18:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant