Happy Password Change Day

If you work at a place like my company, your passwords expire regularly. If you have a job similar to mine, you have a whole mess of systems that you have to change your password on. If you have a personality like mine, this is a really boring task that you’d rather not deal with, but you have to.

Well, I have a solution for you. It’s based on my last post, that magical Python pexpect script. It’s stripped down a little more, but I’m sure you’ll find most of it very familiar if you read through the last one. So, without further ado, I introduce to you my magically delicious password change script:

#!/usr/bin/python
#
# Change a user's password on multiple systems, ensuring that given user
# has valid sudo access. (2 birds, 1 stone)
#
# It requires that you have sudo available on the target(s) and that you
# can run the given command under sudo. It does not require SSH keys be set
# up, since it handles the password dialogs for both SSH login and sudo
# access.
#
# Any vulgarities in this code are the result of being lazy about
# case sensitivity checks, and are not deliberate. If you decide to be
# offended, you need to get over it.

import pexpect
from optparse import OptionParser
import os
import getpass
import signal
import sys
from datetime import datetime

DEBUG = 0
jams = []
misses = []
hits = []

def getTargets(hostspec):
  global DEBUG
  if os.path.isfile(hostspec):
    if DEBUG:
      print "Reading hosts from file "+hostspec
    fh = open(hostspec, 'r')
    hosts=fh.read()
  else:
    if DEBUG:
      print "Using hosts from command line."
    hosts=hostspec
  return hosts.split()

def pullTrigger(target, oldpass, newpass, username):
  global DEBUG, jams, misses, hits
  rangeHot = "\$ "
  # First, we launch the ssh process and get logged in to the target
  # Set a 5 minute timeout on commands, not 30 seconds
  proc = pexpect.spawn("ssh "+target)
  while True:
    index = proc.expect(["The authenticity of host", "assword:", "Permission denied", rangeHot, pexpect.EOF, pexpect.TIMEOUT])
    if index == 0:
      proc.sendline("yes")
    elif index == 1:
      proc.sendline(oldpass)
    elif index == 2:
      jams.append(target)
      if DEBUG:
        print "Dud cartridge. Clearing chamber, proceeding with firing plan..."
      proc.kill(signal.SIGKILL)
      return
    elif index == 3:
      break
    elif index == 4:
      jams.append(target)
      if DEBUG:
        print "Cartridge jammed, clearing chamber, proceeding with firing plan."
      proc.kill(signal.SIGKILL)
      return
    elif index == 5:
      jams.append(target)
      if DEBUG:
        print "Squib load. clearing chamber, proceeding with firing plan."
      proc.kill(signal.SIGKILL)
      return

  # Go root
  if DEBUG:
    print "Becoming root inside expect spawn."
  rangeHot = becomeRoot(proc, oldpass)
  if rangeHot == "EOF":
    misses.append(target)
    if DEBUG:
      print "Missed target low. Proceeding with firing plan."
    proc.kill(signal.SIGKILL)
    return
  if rangeHot == "TIMEOUT":
    misses.append(target)
    if DEBUG:
      print "Missed target high. Proceeding with firing plan."
    proc.kill(signal.SIGKILL)
    return

  # Change password
  proc.sendline("passwd "+username)
  proc.expect(":")
  proc.sendline(newpass)
  proc.expect(":")
  proc.sendline(newpass)

  index = proc.expect([rangeHot, pexpect.EOF, pexpect.TIMEOUT])
  if index != 0:
    misses.append(target)
    if DEBUG:
      print "Missed wide left. Proceeding with firing plan."
    proc.kill(signal.SIGKILL)
    return

  # A hit! A veritable hit! O frabjious day!
  hits.append(target)
  rangeHot = exitRoot(proc)
  proc.sendline("exit")

def exitRoot(proc):
  global DEBUG
  # Quick and dirty. This should really be nicer, but I'm lazy and it's
  # almost guaranteed to work if you actually got this far.
  if DEBUG:
    print "Leaving root shell."
  proc.sendline("exit")
  proc.expect("\$ ")
  return "\$ " 


def becomeRoot(proc, passwd):
  proc.sendline("uname -s")
  index = proc.expect(["SunOS", "Linux"])
  if index == 0:
    proc.sendline("super root-shell")
  elif index == 1:
    proc.sendline("sudo su -")
  while True:
    index = proc.expect(["assword", "\# ", pexpect.EOF, pexpect.TIMEOUT])
    if index == 0:
      proc.sendline(passwd)
    elif index == 1:
      return "\# "
    elif index == 2:
      return "EOF"
    elif index == 3:
      return "TIMEOUT"

def main():
  global DEBUG, jams, misses, hits

  # Set up command line options / arguments
  parser = OptionParser()
  parser.disable_interspersed_args()
  parser.set_defaults(saveResults=True)
  parser.add_option("-H", "--hosts", dest="hostspec", help="hosts to run the command(s) on", metavar="HOSTSPEC", default="pyexphosts")
  parser.add_option ("-d", "--debug", action="store_true", dest="debug", help="print debugging messages")
 
  (options, args) = parser.parse_args()

  if options.debug:
    DEBUG=1
  
  targets = getTargets(options.hostspec)

  username = raw_input("User name to change password for: ")

  oldpass = getpass.getpass("Old password: ")
  newpass = getpass.getpass("New password: ")

  for target in targets:
    if DEBUG:
      print "Launching commands at target "+target
    pullTrigger(target, oldpass, newpass, username)

  if (len(jams)):
    print "Jams noticed:"
    for target in jams:
      print "Target "+target
  if (len(misses)):
    print "Misses noticed:"
    for target in misses:
      print "Target: "+target
  print "Done changing passwords."

if __name__ == "__main__":
  main()

Comments are closed.