How to change a Linux user password from python

2019-02-11 00:15发布

问题:

I'm having problems with changing a Linux user's password from python. I've tried so many things, but I couldn't manage to solve the issue, here is the sample of things I've already tried:

sudo_password is the password for sudo, sudo_command is the command I want the system to run, user is get from a List and is the user who I want to change the password for, and newpass is the pass I want to assign to 'user'

    user = list.get(ANCHOR)
    sudo_command = 'passwd'
    f = open("passwordusu.tmp", "w")
    f.write("%s\n%s" % (newpass, newpass))
    f.close()
    A=os.system('echo -e %s|sudo -S %s < %s %s' % (sudo_password, sudo_command,'passwordusu.tmp', user))
    print A
    windowpass.destroy()

'A' is the return value for the execution of os.system, in this case 256. I tried also

    A=os.system('echo  %s|sudo -S %s < %s %s' % (sudo_password, sudo_command,'passwordusu.tmp', user))

but it returns the same error code. I tried several other ways with 'passwd' command, but whithout succes. With 'chpasswd' command I 've tried this:

    user = list.get(ANCHOR)
    sudo_command = 'chpasswd'
    f = open("passwordusu.tmp", "w")
    f.write("%s:%s" % (user, newpass))
    f.close()
    A=os.system('echo %s|sudo -S %s < %s %s' % (sudo_password, sudo_command,'passwordusu.tmp', user))
    print A
    windowpass.destroy()

also with:

    A=os.system('echo %s|sudo -S %s:%s|%s' % (sudo_password, user, newpass, sudo_command))
    @;which returns 32512
    A=os.system("echo %s | sudo -S %s < \"%s\"" % (sudo_password, sudo_command,  "passwordusu.tmp"))
    @;which returns 256

I tried 'mkpasswd' and 'usermod' too like this:

    user = list.get(ANCHOR)
    sudo_command = 'mkpasswd -m sha-512'
    os.system("echo %s | sudo -S %s %s > passwd.tmp" % (sudo_password,sudo_command, newpass))
    sudo_command="usermod -p"
    f = open('passwd.tmp', 'r')
    for line in f.readlines():
        newpassencryp=line
    f.close()
    A=os.system("echo %s | sudo -S %s %s %s" % (sudo_password, sudo_command, newpassencryp, user))
    @;which returns 32512

but, if you go to https://www.mkpasswd.net , hash the 'newpass' and substitute for 'newpassencryp', it returns 0 which theoretically means it has gone right, but so far it doesn't changes the password.

I've searched on internet and stackoverflow for this issue or similar and tried what solutions exposed, but again,without success.

I would really apreciate any help, and of course, if you need more info i'll be glad to supply it!

Thanks in advance.

回答1:

Try using the '--stdin' option to the passwd command in your pipes. To quote from the man page:

    --stdin
      This option is used to indicate that passwd should read the new
      password from standard input, which can be a pipe.

Another option, if your Linux has the usermod command, as root (or via sudo) you can explicitly set the (encrypted) password using the '-p' option.



回答2:

usermod-based version:

#!/usr/bin/env python
from crypt      import crypt
from getpass    import getpass
from subprocess import Popen, PIPE

sudo_password_callback = lambda: sudo_password # getpass("[sudo] password: ")
username, username_newpassword = 'testaccount', '$2&J|5ty)*X?9+KqODA)7'

# passwd has no `--stdin` on my system, so `usermod` is used instead
# hash password for `usermod`
try:
    hashed = crypt(username_newpassword) # use the strongest available method
except TypeError: # Python < 3.3
    p = Popen(["mkpasswd", "-m", "sha-512", "-s"], stdin=PIPE, stdout=PIPE,
              universal_newlines=True)
    hashed = p.communicate(username_newpassword)[0][:-1] # chop '\n'
    assert p.wait() == 0
assert hashed == crypt(username_newpassword, hashed)

# change password
p = Popen(['sudo', '-S',  # read sudo password from the pipe
           # XXX: hashed is visible to other users
           'usermod',  '-p', hashed, username],
          stdin=PIPE, universal_newlines=True)
p.communicate(sudo_password_callback() + '\n')
assert p.wait() == 0


回答3:

I ran accross the same problem today and I wrote a simple wrapper around subprocess to call the passwd command and feed stdin with the new password. This code is not fool proof and only works when running as root which does not prompt for the old password.

import subprocess
from time import sleep

PASSWD_CMD='/usr/bin/passwd'

def set_password(user, password):
    cmd = [PASSWD_CMD, user]
    p = subprocess.Popen(cmd, stdin=subprocess.PIPE)
    p.stdin.write(u'%(p)s\n%(p)s\n' % { 'p': password })
    p.stdin.flush()
    # Give `passwd` cmd 1 second to finish and kill it otherwise.
    for x in range(0, 10):
        if p.poll() is not None:
            break
        sleep(0.1)
    else:
        p.terminate()
        sleep(1)
        p.kill()
        raise RuntimeError('Setting password failed. '
                '`passwd` process did not terminate.')
    if p.returncode != 0:
        raise RuntimeError('`passwd` failed: %d' % p.returncode)

If you need the output of passwd you can also pass stdout=subprocess.PIPE to the Popen call and read from it. In my case I was only interested if the operation succeeded or not so I simply skipped that part.

Security consideration: Do not use something like echo -n 'password\npassword\n | passwd username' as this will make the password visible in the process list.

SUDO

Since you seam to want to be using sudo passwd <username> I would recommend adding a new line to your /etc/sudoers (use visudo for that!)

some_user ALL = NOPASSWD: /usr/bin/passwd

Sudo will not ask for the password for some_user and the script will run as expected.

Alternatively simply add an extra p.stdin.write(u'%s\n' % SUDO_PASSWORD) line. That way sudo will receive the user password first and then passwd receives the new user password.



回答4:

The user you are running this as must have sudo permission to run the passwd command without a password.

>>> from subprocess import Popen
>>> proc = Popen(['/usr/bin/sudo', '/usr/bin/passwd', 'test', '--stdin'])
>>> proc.communicate('newpassword')