I recently donated a whole bunch of Dell server to ISC to help upgrade f.root-servers.net.

 

They all had Dell DRAC4 cards in them and I wanted to reset them to a known config before donating them.

Dell does provide some Linux software to talk to the cards, but is is mostly binary packages that run under RedHat Enterprise server or CentOS. Various folks have managed to make racadm and the other tools work under other distributions, but it is a headache to setup.

 

I needed to wipe and stage 50 servers or so and I wanted to be able to do it in as simple a manner as possiable. I didn't want to have to wipe the drives, install CentOS, wipe the DRAC card, then reinstall with the real OS, so I ended up writing the below code. It will talk to the DRAC card over the internal serial port and should run on any OS (including FreeBSD and netBSD) that run Python.

 

 

#!/bin/python

#

 

# $Revision:: 16                                           $

 

# $Date:: 2010-01-25 20:01:49 -0500 (Mon, 25 Jan 2010)     $

 

# $Author:: wkumari                                        $

 

# Copyright: Warren Kumari (This email address is being protected from spambots. You need JavaScript enabled to view it.) --  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!'

 

Here is a direct link: rac_reset.py