2

I have the following script

test.py

#!/usr/bin/env python2

from subprocess import Popen, PIPE, STDOUT
proc = Popen(['scp', 'test_file', 'user@192.168.120.172:/home/user/data'], stdout=PIPE, stdin=PIPE, stderr=STDOUT)

out, err = proc.communicate(input='userpass\n')
print('stdout: ' + out)
print('stderr: ' + str(err))

which is meant to copy test_file in a remote directory /home/user/data located at 10.0.0.2 using login for a given user user. In order to do that I must use scp. No key authentification is allowed (don't ask why, it's just how things are and I cannot change them).

Even though I am piping userpass to the process I still get a prompt inside the terminal to enter password. I want to just run test.py on the local machine and then the remote gets the file without any user interaction.

I though that I'm not using communicate() correctly so I manually called

proc.stdin.write('userpass\n')
proc.stdin.flush()
out, err = proc.communicate()

but nothing changed and I still got that password prompt.

rbaleksandar
  • 8,713
  • 7
  • 76
  • 161
  • Can you use public key authentication instead? Then you won't have to worry about passwords at all. – Duncan Apr 16 '18 at 13:06
  • No, as I've mentioned `scp` is the only way. I'm also upset about that but that's how it is. :D I know I can go with `expect`/`pexepect` (but it requires installing some software on the system and I'm not allowed to do that), `sshpass` (same reason as the first point), key authentification is no go etc. – rbaleksandar Apr 16 '18 at 13:08
  • I'm not asking about `scp`, I'm asking whether you can use the public key authentication that `scp` supports instead of using password authentication. – Duncan Apr 16 '18 at 13:17
  • scp is a remote copy method using the SSH protocol for connectivity and authentication. You should be able to use key-based auth here. You have the password for the user on the remote host. What exactly keeps you from creating a key-pair for the user on your source machine to add the public key to the remote user's .ssh/authorized_keys? Corporate policy? – shmee Apr 16 '18 at 13:17
  • 1
    @Duncan, I'm saying that no key authentification is in place. The way things are set up it will also not be available. I will add this to the question in order to omit this sort of discussion. – rbaleksandar Apr 16 '18 at 13:27
  • For a one-time workaround, you could try something like: ` ssh-keygen -t rsa -C 'email@domain.com' ssh-copy-id root@server_box` This is probably bit ideal for an issue that needs to be solved programmatically. – Lefty G Balogh Apr 16 '18 at 13:28
  • @LeftyGBalogh Sadly not an option. :-/ – rbaleksandar Apr 16 '18 at 13:30
  • @rbaleksandar ok, it's just that the standard answer to this would be not to use passwords. `scp` and `ssh` go out of their way to prevent you intercepting the password prompt. My suggestion if you really cannot install any other software and cannot use a secure solution would be to get the source of sshpass, see how it works, and duplicate it. – Duncan Apr 16 '18 at 13:33
  • https://stackoverflow.com/a/43526842/13317 – Kenster Apr 16 '18 at 17:14
  • @Duncan, I know. :D That's why I asked it since it's different than what has already been asked. :) – rbaleksandar Apr 16 '18 at 17:38

1 Answers1

2

When scp or ssh attempt to read a password they do not read it from stdin. Instead they open /dev/tty and read the password direct from the connected terminal.

sshpass works by creating its own dummy terminal and spawning ssh or scp in a child process controlled by that terminal. That's basically the only way to intercept the password prompt. The recommended solution is to use public key authentication, but you say you cannot do that.

If as you say you cannot install sshpass and also cannot use a secure form of authentication then about the only thing you can do is re-implement sshpass in your own code. sshpass itself is licensed under the GPL, so if you copy the existing code be sure not to infringe on its copyleft.

Here's the comment from the sshpass source which describes how it manages to spoof the input:

/*
   Comment no. 3.14159
   This comment documents the history of code.
   We need to open the slavept inside the child process, after "setsid", so that it becomes the controlling
   TTY for the process. We do not, otherwise, need the file descriptor open. The original approach was to
   close the fd immediately after, as it is no longer needed.
   It turns out that (at least) the Linux kernel considers a master ptty fd that has no open slave fds
   to be unused, and causes "select" to return with "error on fd". The subsequent read would fail, causing us
   to go into an infinite loop. This is a bug in the kernel, as the fact that a master ptty fd has no slaves
   is not a permenant problem. As long as processes exist that have the slave end as their controlling TTYs,
   new slave fds can be created by opening /dev/tty, which is exactly what ssh is, in fact, doing.
   Our attempt at solving this problem, then, was to have the child process not close its end of the slave
   ptty fd. We do, essentially, leak this fd, but this was a small price to pay. This worked great up until
   openssh version 5.6.
   Openssh version 5.6 looks at all of its open file descriptors, and closes any that it does not know what
   they are for. While entirely within its prerogative, this breaks our fix, causing sshpass to either
   hang, or do the infinite loop again.
   Our solution is to keep the slave end open in both parent AND child, at least until the handshake is
   complete, at which point we no longer need to monitor the TTY anyways.
 */

So what sshpass is doing is opening a pseudo terminal device (using posix_openpt), then forks and in the child process makes the slave the controlling pt for the process. Then it can exec the scp command.

I don't know if you can get this to work from Python, but the good news is the standard library does include functions for working with pseudo terminals: https://docs.python.org/3.6/library/pty.html

Duncan
  • 92,073
  • 11
  • 122
  • 156
  • I'm rather intrigued by the first paragraph of your reply. If `scp` and `ssh` use a `/dev/tty` is it not possible to write to that `tty`? Or is it encapsulated in such a way that one cannot possibly access it? – rbaleksandar Apr 16 '18 at 17:41
  • Yes, I've extended my answer. sshpass does indeed create a new tty and makes sure that the command will see that as the controlling terminal. It sounds like it is still a bit messy. – Duncan Apr 17 '18 at 08:06
  • 1
    Damn...I will make another attempt to fight against all this. It's too much work for 1)something that is already working and 2)using GPL code is a no go. Still marking this as the answer since it does give some pointers where to look. – rbaleksandar Apr 17 '18 at 08:33