# hostSentry - Login Anomaly Detector UTMP library # Author: Craig H. Rowland # Created: 10-6-98 # # Send all changes/modifications/bugfixes to the above address. # # This software is Copyright(c) 1997-98 Craig H. Rowland # # Disclaimer: # # All software distributed by Craig H. Rowland ("the author") and # Psionic Systems is distributed AS IS and carries NO WARRANTY or # GUARANTEE OF ANY KIND. End users of the software acknowledge that # they will not hold the author, Psionic Systems, and any employer of # the author liable for failure or non-function of a software # product. YOU ARE USING THIS PRODUCT AT YOUR OWN RISK # # Licensing restrictions apply. See the license that came with this # file for more information or visit http://www.psionic.com for more # information. # # This software is NOT GPL NOR PUBLIC DOMAIN so please read the license # before modifying or distributing. Contact the above address if you have # any questions. # # $Id: hostSentryUtmp.py,v 1.1 1999/03/22 04:56:49 crowland Exp crowland $ from hostSentryCore import * import hostSentryConfig import hostSentryLog import struct import sys import string UTMP_PATH = '/var/log/wtmp' # These methods are still rough around the edges and # only support Linux right now. I'll add BSD support # once the code stabilizes a bit. This will eventually # be converted to a Python extension module using native # getutent() calls under Unix. class hostSentryUtmp(hostSentryCore): def __init__(self, utmpFile = UTMP_PATH): self.setLogLevel() self.utmpFile = utmpFile def read(self, data = 0, offset = 0, whence = 0): logLevel = self.getLogLevel() if logLevel > 0: hostSentryLog.log('debug: hostSentryUtmp: read: Reading WTMP entry') try: utmp = open(self.utmpFile) utmp.seek(offset, whence) if data == 0: return utmp.read() else: return utmp.read(data) except: raise hostSentryError('adminalert: Error reading utmp file: %s' % sys.exc_value[0]) ################################################################################### # parse Method # # This method takes a utmpEntry and a format string # to parse out the username, tty, and hostname data # # The format string is formed as: # # utmpEntryLength/ttyOffset:ttyLen/usernameOffset:usernameLen/hostnameOffset:hostnameLen # # For example on RedHat: # # utmp record size is: 384 bytes # tty entry offset is: 8 bytes # tty entry size from offset is: 32 bytes # username entry offet is: 44 bytes # username entry size from offset is: 32 bytes # hostname entry offset is: 76 bytes # hostname entry size from offset: 256 bytes # # This would be formed as "384/8:32/44:32/76:256" # and passed as the second argument. # # This method returns a filled in utmpObj object. # #################################################################################### def parse(self, utmpEntry, utmpFormat): logLevel = self.getLogLevel() if logLevel > 0: hostSentryLog.log('debug: hostSentryUtmp: parse: Parsing WTMP entry') try: utmpSizeForm, utmpTtyForm, utmpUsernameForm, utmpHostnameForm = \ string.split(utmpFormat, "/") utmpTty = string.split(utmpTtyForm, ":") utmpUsername = string.split(utmpUsernameForm, ":") utmpHostname = string.split(utmpHostnameForm, ":") except: raise hostSentryError(sys.exc_value[0]) try: userObj = utmpObj() # Set the TTY entryLen = string.atoi(utmpTty[1]) unpackLen = utmpTty[1] + 's' offset = string.atoi(utmpTty[0]) userObj.setTty(self.eraseNulls(struct.unpack(unpackLen, utmpEntry[offset:offset + entryLen])[0])) # Set the Username entryLen = string.atoi(utmpUsername[1]) unpackLen = utmpUsername[1] + 's' offset = string.atoi(utmpUsername[0]) userObj.setUsername(self.eraseNulls(struct.unpack(unpackLen, utmpEntry[offset:offset + entryLen])[0])) # Set the Hostname entryLen = string.atoi(utmpHostname[1]) unpackLen = utmpHostname[1] + 's' offset = string.atoi(utmpHostname[0]) userObj.setHostname(self.eraseNulls(struct.unpack(unpackLen, utmpEntry[offset:offset + entryLen])[0])) return userObj except: raise hostSentryError (sys.exc_value[0]) # Copies non-nulls only into data area. def eraseNulls(self, data): result = '' for x in range(len(data)): if data[x] != '\000': result = result + data[x] else: break return result class utmpObj: def __init__(self): self.__username='' self.__tty = '' self.__hostname = '' def setUsername(self, username): self.__username = username def getUsername(self): return self.__username def setTty(self, tty): self.__tty = tty def getTty(self): return self.__tty def setHostname(self, hostname): self.__hostname = hostname def getHostname(self): return self.__hostname if __name__ == '__main__': # Slackware # WTMP_FORMAT = '56/8:12/28:8/36:16' # RedHat WTMP_FORMAT = '384/8:32/44:32/76:256' test=hostSentryUtmp('/var/log/wtmp') test.setLogLevel(99) offset = 0 utmpsize = string.atoi(string.split(WTMP_FORMAT, "/")[0]) try: while 1: utmpEntry = test.parse(test.read(utmpsize, offset), WTMP_FORMAT) print 'Username : ' + utmpEntry.getUsername() print 'TTY : ' + utmpEntry.getTty() print 'Hostname : ' + utmpEntry.getHostname() # If the username is '' it's a logout if utmpEntry.getUsername() == '': print 'Status : LOGOUT' else: print 'Status : LOGIN' offset = offset + utmpsize except hostSentryError, errorMessage: print errorMessage