81

Every once in a while I will do something like

ssh user@host sudo thing

and I am reminded that ssh doesn't allocate a pseudo-tty by default. Why doesn't it? What benefits would I be losing if I aliased ssh to ssh -t?

Chas. Owens
  • 2,013
  • 2
  • 20
  • 24
  • 1
    > I am reminded that ssh doesn't allocate a psuedo-tty What happens? It would enrich the question to understand what the problem is. – Air Sep 24 '14 at 21:30
  • 8
    @Air There isn't a problem I was trying to fix. There was a choice in how ssh is implemented that I was trying to understand. The question is very clear and the answer by Andrew B addresses the question nicely. The answer can be summed up like this: running `ssh -t` always is bad because it can cause some commands to break in weird ways. Whereas, running a command that needs a PTY without one results in a clear error message that you need a terminal. – Chas. Owens Sep 24 '14 at 22:18

5 Answers5

86

The primary difference is the concept of interactivity. It's similar to running commands locally inside of a script, vs. typing them out yourself. It's different in that a remote command must choose a default, and non-interactive is safest. (and usually most honest)

STDIN

  • If a PTY is allocated, applications can detect this and know that it's safe to prompt the user for additional input without breaking things. There are many programs that will skip the step of prompting the user for input if there is no terminal present, and that's a good thing. It would cause scripts to hang unnecessarily otherwise.
  • Your input will be sent to the remote server for the duration of the command. This includes control sequences. While a Ctrl-c break would normally cause a loop on the ssh command to break immediately, your control sequences will instead be sent to the remote server. This results in a need to "hammer" the keystroke to ensure that it arrives when control leaves the ssh command, but before the next ssh command begins.

I would caution against using ssh -t in unattended scripts, such as crons. A non-interactive shell asking a remote command to behave interactively for input is asking for all kinds of trouble.

You can also test for the presence of a terminal in your own shell scripts. To test STDIN with newer versions of bash:

# fd 0 is STDIN
[ -t 0 ]; echo $?

STDOUT

  • When aliasing ssh to ssh -t, you can expect to get an extra carriage return in your line ends. It may not be visible to you, but it's there; it will show up as ^M when piped to cat -e. You must then expend the additional effort of ensuring that this control code does not get assigned to your variables, particularly if you're going to insert that output into a database.
  • There is also the risk that programs will assume they can render output that is not friendly for file redirection. Normally if you were to redirect STDOUT to a file, the program would recognize that your STDOUT is not a terminal and omit any color codes. If the STDOUT redirection is from the output of the ssh client and the there is a PTY associated with the remote end of the client, the remote programs cannot make such a distinction and you will end up with terminal garbage in your output file. Redirecting output to a file on the remote end of the connection should still work as expected.

Here is the same bash test as earlier, but for STDOUT:

# fd 1 is STDOUT
[ -t 1 ]; echo $?

While it's possible to work around these issues, you're inevitably going to forget to design scripts around them. All of us do at some point. Your team members may also not realize/remember that this alias is in place, which will in turn create problems for you when they write scripts that use your alias.

Aliasing ssh to ssh -t is very much a case where you'll be violating the design principle of least surprise; people will be encountering problems they do not expect and may not understand what is causing them.

Andrew B
  • 31,858
  • 12
  • 90
  • 128
  • 13
    One almost gets the impression that I've worked on a team that has done this... – Andrew B May 06 '14 at 17:14
  • At the scope covered by this answer, it's a great. But the question had a wider scope, essentially wanting to know ALL the differences (what to expect). Additional info: At the process level, -t will FIRST allocate a tty and THEN run a shell (along the way, sourcing /etc/profile and ~/.bash_profile) and THEN run the command. Without -t, ssh will INSTEAD source different env files (/etc/bash.bashrc, then ~/.bashrc) and THEN run your command. This means you can see very different behavior in each: shorter $PATH, maybe a bash 'unary' error because the ENV var you assumed isn't there... – Scott Prive Dec 01 '18 at 18:08
  • @Crossfit We covered the topic of login shells vs. non in the comments of another answer, but we didn't go into an exhaustive breakdown of environmental differences because the OP had already considered the question answered for their particular needs. Please feel free to add detail where you see room to do so as it may help others who come upon this Q&A. – Andrew B Dec 01 '18 at 22:02
39

SSH escape/control characters and transfer of binary files

One advantage that hasn’t been mentioned in the other answers is that when operating without a pseudo-terminal, the SSH escape characters such as ~C are not supported; this makes it safe for programs to transfer binary files which may contain these and other control characters.

Without a pseudo-terminal the characters are transferred to the remote host “as is”:

$ printf "one\ntwo\n~Cthree\n" | ssh user@host tee 1
one
two
~Cthree

If you try to forcing the allocation of a pseudo-terminal, the inclusion of the ~C causes the ssh> prompt to be printed to allow the user to enter an SSH command, interrupting the transfer.

$ printf "one\ntwo\n~Cthree\n" | ssh -tt user@host tee 2

ssh>
one
two
three
one
two
three

A ~. sequence is worse as it results in no data being transferred:

$ printf "one\ntwo\n~.three\n" | ssh -tt user@host tee 2
Connection to host closed.

Software flow control

Software flow control characters (XON/XOFF) may also be treated specially when a pseudo-terminal is allocated.

$ printf "one\ntwo\n^Sthree\n" | ssh user@host tee 1
one
two
three

$ ssh user@host cat -vet 1
one$
two$
^Sthree$

With a pseudo-terminal, the Ctrl-S character is interpreted as a signal to pause the input stream so no more data can be sent after this character is encountered (unless it’s followed by a Ctrl-Q character later in the input stream).

In this case, forcing the allocation of a pseudo-terminal results in the file on the remote end being empty.

$ printf "one\ntwo\n^Sthree\n" | ssh -tt user@host tee 2
one
two

In this case, forcing the allocation of a pseudo-terminal results in the file on the remote end containing all three lines but without the control characters:

$ printf "one\ntwo\n^Sthree^Q\n" | ssh -tt user@host tee 2
one
two
three
one
two
three

$ ssh user@host cat -vet 2
one$
two$
three$

Conversion of line-ending characters

With a pseudo-terminal, Line Feed characters (decimal 10, hex 0A in ASCII) are also translated to CRLF sequences (hex 0D0A in ASCII) .

From the previous example:

$ ssh -t user@host cat 1 | cat -vet
one^M$
two^M$
^Sthree^M$
Connection to host closed.

Copy a binary file using a pseudo-terminal

$ ssh -t anthony@remote_host 'cat /usr/bin/free' > ~/free
Connection to remote_host closed.

Copy a binary file without using a pseudo-terminal:

$ ssh anthony@remote_host 'cat /usr/bin/free' > ~/free2

The two files aren’t the same:

$ diff ~/free*
Binary files /home/anthony/free and /home/anthony/free2 differ

The one which was copied with a pseudo-terminal is corrupted:

$ chmod +x ~/free*
$ ./free
Segmentation fault

while the other isn’t:

$ ./free2
             total       used       free     shared    buffers     cached
Mem:       2065496    1980876      84620          0      48264    1502444
-/+ buffers/cache:     430168    1635328
Swap:      4128760        112    4128648

Transferring files over SSH

This is particularly important for programs such as scp or rsync which use SSH for data transfer. This detailed description of how the SCP protocol works explains how the SCP protocol consists of a mixture of textual protocol messages and binary file data.


OpenSSH helps protects you from yourself

It’s worth noting that even if the -t flag is used, the OpenSSH ssh client will refuse to allocate a pseudo-terminal if it detects that its stdin stream is not a terminal:

$ echo testing | ssh -t anthony@remote_host 'echo $TERM'
Pseudo-terminal will not be allocated because stdin is not a terminal.
dumb

You can still force the OpenSSH client to allocate a pseudo-terminal with -tt:

$ echo testing | ssh -tt anthony@remote_host 'echo $TERM'
xterm

In either case, it (sensibly) doesn’t care if stdout or stderr are redirected:

$ ssh -t anthony@remote_host 'echo $TERM' >| ssh_output
Connection to remote_host closed.
Anthony Geoghegan
  • 2,800
  • 1
  • 23
  • 34
  • 2
    That was a very interesting point that I hadn't considered, thanks for adding this answer! – Jenny D Jan 14 '16 at 14:54
  • 1
    I could reproduce the result but the first differing byte wasn't one of SSH's control sequences but a \n that had been replaced by \r\n. – Martin Dorey Jun 23 '20 at 18:33
  • @MartinDorey Thanks for that. I've had to spend the last few hours coming up other proofs of conception. :) I've now edited the answer to improve it with more examples of characters being interpreted differently when a PTY is allocated. Well spotted! – Anthony Geoghegan Jun 24 '20 at 00:16
4

On remote host we have to do with this setting:

/etc/sudoers
...
Defaults requiretty

Without sudo

$ ssh -T user@host echo -e 'foo\\nbar' | cat -e
foo$
bar$

And with sudo

$ ssh -T user@host sudo echo -e 'foo\\nbar' | cat -e
sudo: sorry, you must have a tty to run sudo

With sudo we get the extra carriage return

$ ssh -t user@host sudo echo -e 'foo\\nbar' | cat -e
foo^M$
      bar^M$
            Connection to localhost closed.

The solution is to disable the translate newline to carriage return-newline with stty -onlcr

$ ssh -t user@host stty -onlcr\; sudo echo -e 'foo\\nbar' | cat -e
foo$
    bar$
        Connection to localhost closed.
  • 1
    Nice but the output is indented /: Do you happen to know if you can auto carriage-return it (unix-like) ? – Boop Apr 25 '18 at 07:41
1

Think about backward-compatibility.

The 2 primary modes of ssh are interactive-login with a pty, and specified-command without a pty, because those were the exact capabilities of rlogin and rsh respectively. ssh needed to provide a superset of rlogin/rsh features to be successful as a replacement.

So the defaults were decided before ssh was born. Combinations like "I wanna specify a command and get a pty" had to be accessed with new options. Be glad that at least we have that option now, unlike when we were using rsh. We didn't trade away any useful features to get encrypted connections. We got bonus features!

mgarort
  • 103
  • 2
0

From man ssh:

 -t      Force pseudo-tty allocation.  This can be used to execute arbi-
         trary screen-based programs on a remote machine, which can be
         very useful, e.g. when implementing menu services.  Multiple -t
         options force tty allocation, even if ssh has no local tty.

This allows you to get a "shell" of sorts to the remote server. For servers that do not grant shell access but allow SSH (i.e, Github is a known example for SFTP access), using this flag will cause the server to reject your connection.

The shell also has all your environmental variables (like $PATH) so executing scripts generally needs a tty to work.

Nathan C
  • 14,901
  • 4
  • 42
  • 62
  • 1
    This does not answer the question. I already know how to allocate a pseudo-tty. I want to know why I shouldn't just always allocate one. – Chas. Owens May 06 '14 at 14:59
  • 1
    @Chas.Owens Because, as I pointed out in the answer some SSH servers do *not* allow tty access and it'll drop the connection if you request one from the server. – Nathan C May 06 '14 at 15:10
  • 6
    I think you may be getting some of your terminology confused. It's not a shell simply because a PTY is associated with it. There are generally three types of shell: `non-interactive`, `interactive`, and `login`. `login` is an additional characteristic of the other two shell types. The permutations of these three determine which files are sourced on login, which in turn influences how the environment will be initialized. (variables, as you were mentioning) – Andrew B May 06 '14 at 16:29
  • 3
    @AndrewB You're right...I learned something from this question too. :) – Nathan C May 07 '14 at 00:09