Brian H.
Brian H.

Reputation: 2235

How can I use Python to automate setting a password using the Unix pass command line program

I'm trying to automate setting new passwords using the Unix pass program. I understand that there is a Python library, pexpect, that might help, but I would like to avoid using third-party libraries.

When using a terminal, the flow looks like this:

$ pass insert --force gmail
>> Enter password for gmail: <type in password using masked prompt>
>> Retype password for gmail: <reenter password>

What I would like my function to do:

  1. Run the command pass insert --force {entry_name}
  2. Capture the output (and echo it for testing)
  3. Check output for the presence of 'password for gmail', and if True
    • write '{password}\n' to stdin
    • write '{password}\n' to stdin again
  4. Echo any errors or messages for testing

Issues:

I'm stuck on step 2. The subprocess either hangs indefinitely, times out with an error, or produces no output.

Attempts:


Code:

def set_pass_password(entry_name, password):
    from subprocess import Popen, PIPE

    command = ['pass', 'insert', '--force', entry_name]

    sub = Popen(command, stdin=PIPE, stdout=PIPE, stderr=PIPE, universal_newlines=True)

    # At this point I assume that the command has run, and that there is an "Enter password..." message
    message = sub.stdout.read()  # also tried readline() and readlines()
    print(message) # never happens, because process hangs on stdout.read()

    if 'password for {}'.format(entry_name) in message:
        err, msg = sub.communicate(input='{p}\n{p}\n'.format(p=password))
        print('errors: {}\nmessage: {}'.format(err, msg))

Upvotes: 0

Views: 1773

Answers (1)

Leo K
Leo K

Reputation: 5354

Edit: the original answer was about passwd, which is what's used to set passwords. I noticed late that you use pass, which is a keystore (doesn't actually change the Unix password). The pass program works differently and will not print a prompt if stdin is not a tty. Therefore the following very simple program works:

def set_pass_password(entry_name, password):
    from subprocess import Popen, PIPE

    command = ['pass', 'insert', '--force', entry_name]

    sub = Popen(command, bufsize=0, stdin=PIPE, stdout=PIPE, stderr=PIPE)

    err, msg = sub.communicate(input='{p}\n{p}\n'.format(p=password))
    print('errors: {}\nmessage: {}'.format(err, msg))

if __name__ == "__main__":
    set_pass_password("ttt", "ttt123asdqwe")

(you will see that both stderr and stdout are empty, if the command succeeded).

For the passwd command:

FYI: the passwd command outputs the prompt to stderr, not stdout.

NOTE: rather than sending the password twice in the same 'write', you might need to wait for the second prompt before sending the password again.

For this simple case, code similar to yours should work, but in general you should use select on all the pipes and send/receive data when the other side is ready, so you don't get deadlocks.

Upvotes: 1

Related Questions