5

First of all, if I do a simple ssh admin@example.com "cat backup.tar" > backup.tar this is not happening! So, there is something else converting it which I don't know.

I have the requirement to provide a backup of the current state of the system without giving access to the server. What I have done is the following:

  1. Create a new user. Let's call him username
  2. Allow only access via ssh-key
  3. Put the keys into the authorized-keys
  4. Change the command / shell in /etc/passwd to a custom script for username
  5. Custom script gets the data by doing a database dump and copy some files and eventually creates a tar archive to the stdout
  6. Make sure that there is no additional output to stdout.

To get the data you need to do: ssh username@example.com > backup.tar.

Now, nearly everything is working fine except that the line ending is changed. The tar gets bigger and I can see that every line has the dos line ending with a ^M symbol. If I force dos2unix on the binary tar file the file matches the file on the server.

Why is this happening and what is changing the line ending? I even simplified it to the following: The script on the server for username linked in /etc/passwd just make a cat .viminfo and the line endings are still transferred wrong when using the above ssh command to login as username and redirect the output to a file on my local machine.

Enak
  • 153
  • @fra-san Thank you very much! That works. After knowing the solution from your comment and search what a pseudo-terminal is and why you should deactivate, I can find my use case at other exchange pages, too. Have tried to search for my problem and find a solution to that for more than an hour before. – Enak Apr 14 '21 at 11:51
  • I turned my comment into a full answer (and removed the comment). – fra-san Apr 14 '21 at 13:45
  • 1
    Note: ssh username@example.com whatever will run the "shell" (the script used instead of a shell) with command line arguments -c and whatever. It totally depends on the script how (if) the script changes its behavior because of them. In general any program one wants to use as a shell should accept -c code. The program may ignore -c but it shall not be surprised nor misled by it. If your script recognizes -c then keep in mind ssh users logging in as username are able to use it. An edge case is when -c enables some functionality you don't want these users to have access to. – Kamil Maciorowski Apr 14 '21 at 16:14

2 Answers2

6

What is likely happening: when ssh is invoked with no command argument (and no RemoteCommand option specified), a pseudo-terminal is by default allocated on the remote system for the session. That happens regardless of the command the remote system is configured to run. A pseudo-terminal is usually configured to translate line feed characters into carriage return + line feed sequences (LFCRLF, see this answer for a thorough explanation). Hence, the output of your script is written to a pseudo-terminal device, which alters it, and then sent to the client side.

The allocation of a pseudo-terminal can be prevented in several ways, including:

  • invoking ssh with the -T option on the client side (or using RequestTTY no in ssh's configuration file (likely ~/.ssh/config));

  • prepending no-pty (note the white space) to the user's key in authorized_keys on the remote system;

  • using PermitTTY no in the configuration of the SSH server on the remote system (possibly /etc/ssh/sshd_config), likely using a Match conditional block to make it only affect a specific user); for instance:

    Match User="username"
      ForceCommand /path/to/your/script
      DisableForwarding yes
      PermitTTY no
    

    (which assumes a working command interpreter is set for the user in /etc/passwd; you may then want to lock the user's password and make sure they have no other means of logging in);

  • (in your case, invoking ssh with a command argument should work too (e.g. ssh user@host :); though I would consider this no more than a workaround).

Of course, the most suitable option depends on your use case and, in particular, on whether the user is supposed to be allowed to choose.

See also:

fra-san
  • 10,205
  • 2
  • 22
  • 43
0

I am using ssh to create a tunnel for my python script. The ssh command line doesn't do anything, it has no arguments, so it tries to open a shell and a pseudo-tty.

After ssh is launched using subprocess.Popen, the newline characters of my python script get broken into just linefeed, no carriage return, so the output of the python script goes to hell. On top of that, the python script will not run under pytest for reasons I don't understand that are related to the pseudo-tty. I could get it to run under pytest with -tt supplied to the ssh, but still the normal python stdout gets corrupted. -T does not work with pytest.

I solved the problem by putting in a very long sleep command in the ssh command line; i.e. the equivalent of ssh -L 1234:remote:1234 user@remote sleep 1000000. Then no pseudo-tty funny business happens and the ssh tunnel will self close after 11 days. I only need the tunnel open for testing purposes, so the timeout is not a problem.