#!/bin/python
#
# $Revision:: 16                                           $
# $Date:: 2010-01-25 20:01:49 -0500 (Mon, 25 Jan 2010)     $
# $Author:: wkumari                                        $
# Copyright: Warren Kumari (warren@kumari.net) --  2010
#

"""
This tries to connect to a DRAC4 card in a Dell server and 
resets the password, IP, mask and gateway. 
It assumes that the DRAC card will show up on /dev/ttyS1
and that you have pySerial installed.

It is neither pretty nor elegant, but I needed to reset the
DRAC card on a bunch ofservers and this just works.
It does no error checking, make completely brick your system, 
may cuase early baldness, etc.

Options:
  --ip: The IP address to set the DRAC to.
  --mask: The netmask (dotted quad).
  --gw: The gateway to use.

Example:
  ./rac_reset.py --ip=192.168.0.12 --mask=255.255.255.0 --gw=192.168.0.1

 """
import getopt
import sys
import time
try:
  import serial
except:
  print ('Unable to import pySerial module.\n'
         'On Ubuntu (and similar) you may be able to fix this with:\n'
         'apt-get install python-serial')
  sys.exit(-1)

PORT = '/dev/ttyS1'
FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.' for x in range(256)])
OK_STRING = '\x02\x60\x0A\x00\x00\x00\x00\x00\x00\x00\x96\x03'
CALVIN_MD5 = 'e6e66b8981c1030d5650da159e79539a'

def rac_connect():
  s = serial.Serial(PORT, timeout = 10)
  if s.isOpen():
    print 'Connected to: %s' % s.portstr
  else:
    print "Unable to open: %s", s.portstr
    sys.exit(-1)
  return s
    
def xmit(str):
  s=rac_connect()
  print 'Going to send: %s' % str
  s.write(str)
  time.sleep(1)
  data = ''
  if s.inWaiting:
    data = data + s.read(s.inWaiting())
    time.sleep(0.5)
  while s.inWaiting():
    data = data + s.read(s.inWaiting())
    time.sleep(0.5)
  if data:
     if data == OK_STRING:
       print 'Success!'
     else:
       print "Got an error: %s\n" % dump(data, len(data))
  else:
    print 'Got no reply!'
  s.close()
  # And give the serial port a bit to settle down.
  time.sleep(2)

def dump(src, length):
  """Prints the input in hex notation."""
  N=0; result=''
  while src:
    s,src = src[:length],src[length:]
    hexa = ' '.join(["%02X"%ord(x) for x in s])
    s = s.translate(FILTER)
    result += "%04X   %-*s   %s\n" % (N, length*3, hexa, s)
    N+=length
  return result

def checksum(str):
  """Takes a string an calculates the RAC checksum.

  The RAC checksum seems to be made by adding all
  the charaters in the string mod 256 and then
  negating that..

  Args:
    str: A string that we want the checksum for.

  Returns: A char to be appended to the str to
           make the checksum correct.
  """
  a = 0
  for char in str:
    a = a + ord (char)
    a = a % 256
  checksum = 256 - a
  return chr(checksum)

def make_str(command, cmd_no):
  """Returns a string suitable to be handed to the RAC.

  It looks like the DRAC expects a struct with various
  bits filled in with, um, something. This takes a
  command (like "racdump") and returns it embedded in
  the struct.

  Args:
    command: A string containing a command (e.g: "racdump")
    cmd_no : An integer, how many cammands we have run. It
      appears that the DRAC *may* uses this so that it can
      have multiple outstanding commands. Looks like not
      actually used.
  Returns:
    A string, suitable to be passed to the DRAC socket. Includes
    checksum."""
  PREFIX = "\2"
  PAD = "\0"
  SUFFIX = "\3"
  length = chr(len(command) + 6)
  command_str = ("P" + length + PAD + chr(cmd_no) + \
                 command + PAD)
  str  = (PREFIX + command_str)
  str = str + checksum(command_str) + SUFFIX
  return str

def usage():
   print '%s' % __doc__

if __name__ == "__main__":
  try:
    opts, args = getopt.getopt(sys.argv[1:], "hi:m:g:v", 
                                     ["help", "ip=", 'mask=', 'gw='])
  except getopt.GetoptError, err:
    print 'Option %s' % err
    sys.exit(2)

  verbose = False
  ip = ''
  mask = ''
  gw = ''
  for o, a in opts:
    if o == "-v":
      verbose = True
    elif o in ("-h", "--help"):
      usage()
      sys.exit()
    elif o in ("-i", "--ip"):
      ip = a
    elif o in ("-m", "--mask"):
      mask = a
    elif o in ("-g", "--gw"):
      gw = a
    else:
      assert False, "unhandled option"

  if not ip or not mask or not gw:
    print 'Error: must supply --ip, --mask and --gw!'
    sys.exit(-1)
  print 'Welcome to the RAC reset utility.'
  print 'This will reset the DRAC4 root password to "calvin",'
  print 'the IP to %s, the mask to %s and the gateway to %s\n' % (
      ip, mask, gw)
#  ip = raw_input('Please enter IP: ')
#  mask = raw_input('Please enter mask: ')
#  gw = raw_input('Please enter gateway: ')

  cmd_no = 0
  xmit(make_str('setoid -g cfgUserAdmin -o cfgUserAdminPassword -i 1 %s' % CALVIN_MD5, cmd_no))
  xmit(make_str('setoid -g cfgLanNetworking -o cfgNicGateway %s' % gw.strip(),cmd_no))
  xmit(make_str('setoid -g cfgLanNetworking -o cfgNicNetmask %s' % mask.strip(),cmd_no))
  xmit(make_str('setoid -g cfgLanNetworking -o cfgNicIpAddress %s' % ip.strip(), cmd_no))
  print 'Done!'

                
