1

I'm writing a Python script that changes the username and password of a Linux account user - it's part of a larger internal web-gui system that queues up password change requests from apache2 (which can't run as root), and then changes the passwords itself. The python script itself obviously must run as root in order to change passwords.

The password change function is pretty straightforward:

def chpasswd(user, passwd):
    if os.getuid() != 0:
        syslog.syslog("Error: chpasswd.py must be run as root")
        return

    proc = Popen(
        ['/usr/sbin/chpasswd'], 
        stdin = subprocess.PIPE, 
        stdout = subprocess.PIPE, 
        stderr = subprocess.PIPE
    )
    print "Changing: " + user + ':' + passwd
    out, err = proc.communicate(user + ':' + passwd)
    proc.wait()

    print out

    if proc.returncode != 0:
        print "Error: Return code", proc.returncode, ", stderr: ", out, err
        if out:
            syslog.syslog("stdout: " + out)
        if err:
            syslog.syslog("stderr: " + err)

The print statements are just there for temporary debugging. This runs fine and doesn't report any errors - there's nothing on out or err; but for some reason the actual UNIX password simply isn't changed.

The script which invokes this function is listening on a locally bound TCP socket. When it receives a change password request (in the form of user:password - later to be encrypted but for now plaintext) it adds it to a queue and then invokes the chpasswd function.

So, typical usage would be:

# telnet localhost 7001
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
jsmith:mynewpassword

When the server is running in a bash window (not as a daemon) it prints out:

# python chpasswd.py
Starting Password Server
New connection from: 127.0.0.1
Changing: jsmith:mynewpassword

The last statement, you can see, is the print statement in my chpasswd function.

But after doing the above, when I actually try to login as a user using the new password, I get:

$ su jsmith
Password: 
su: Authentication failure

Is there some obvious thing I'm doing wrong here? My suspicion was that somehow the connection with Popen is not actually closing, or perhaps the single line user:password text is not being transmitted. So I tried doing something like:

out, err = proc.communicate(user + ':' + passwd + '\x04')

Notice the extra \x04 character at the end, indicating End Of Transmission. Adding this in still didn't get it to work however - the password remained unchanged.

I'm running this on Debian Wheezy, in case it makes any difference.

Update:

Investigating further, I can see that my chpasswd function actually is changing the password - if I cat the /etc/shadow file before and after connecting to my password server, I see there is a different hash.

It's just that when I try to authenticate using the plaintext password, it doesn't work. Therefore, my suspicion is that somehow, the communication with Popen is either adding additional characters, or losing characters somehow. Of course, since /etc/shadow is a hash, I can't figure out exactly what's going on here.

7
  • Have you tried just printing stdout to see if everything is going as expected? Also you don't PIPE stdout or stderr, so that's a problem too for communicate Commented Oct 27, 2014 at 19:36
  • Yeah - I just updated to try that. Nothing is on out or err Commented Oct 27, 2014 at 19:40
  • Just FYI, you don't need to put a big bold notice when you edit your question, we can see a full revision history and things get edited all the time. Related discussion. Commented Oct 27, 2014 at 19:57
  • 2
    Try printing repr(passwd). I don't know how you've setup your server, but it could be there's an erroneous \r\n on the end of the password if you aren't stripping the data you get over the socket. Commented Oct 27, 2014 at 20:17
  • That was it... so stupid of me, I forget to strip the extra \r\n that the telnet client uses. And to think... I was ready to download the source for chpasswd.c and see exactly what's going on... Anyway, put that as an answer and I'll upvote + accept Commented Oct 27, 2014 at 20:26

1 Answer 1

1

The problem in this particular instance was that telnet adds "\r\n" after you press return on entering text. Since your server was not stripping the data of whitespace this was preserved when changing the password.

It is possible to get telnet to not send the carriage return and newline characters by ending a line with the end-of-transmission character (EOT). You can do this by pressing Ctrl-D.

eg

$ telnet localhost 7001
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
jsmith:mynewpassword^DChanging: jsmith:mynewpassword

Alternatively you can pipe the line into telnet

echo -n jsmith:mynewpassword | telnet localhost 7001

Obviously, you'll only want to do this for testing or the new password will end up in your shell history. (The -n argument suppresses the printing of newline characters by echo)

Or you might want to do away with telnet altogether and use netcat instead.

echo -n jsmith:mynewpassword | netcat localhost 7001
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.