#! /usr/bin/env python import ConfigParser import fileinput import getopt import os import re import string, sys, time #common failure codes KerberosFailureCodes={ "0x1": "Client entry in database has expired", "0x2": "Server entry in database has expired", "0x3": "Requested protocol version not supported", "0x12": "Account disabled expired or locked out", "0x13": "Credentials for server have been revoked", "0x14": "Ticket Granting Ticket has been revoked", "0x15": "Client not yet valid", "0x16": "Server not yet valid", "0x17": "Password expired", "0x18": "Bad password", "0x19": "Additional pre-authentication required", "0x20": "Ticket Expired", "0x25": "Clock skew too great", "0x26": "Incorrect net address", } #Critical items we're after. They're followed by : then the value corresponding to the item. #Order is important, longer matches first please descriptionKeys=( "User whose credentials were used", "Service Principal Names", "Pre-Authentication Type", "Certificate Issuer Name", "Ticket Encryption Type", "Source Network Address", "Source Network Address", "Authentication Package", "User Account Control", "User Principal Name", "Target Account Name", "Supplied Realm Name", "Caller Machine Name", "Sam Account Name", "AllowedToDelegateTo", "Transited Services", "Target Server Name", "Target Server Info", "Source Workstation", "Creator Process ID", "Changed Attributes", "User Workstations", "Target Logon GUID", "Target Account ID", "Primary User Name", "Password Last Set", "Client Address", "Caller Process ID", "Workstation Name", "Target User Name", "Primary Logon ID", "Primary Group ID", "New Account Name", "Logon attempt by", "Client User Name", "Caller User Name", "Additional Info2", "Operating System", "User Parameters", "Image File Name", "Client Logon ID", "Caller Logon ID", "Additional Info", "Account Expires", "Ticket Options", "Substatus code", "Primary Domain", "Operation Type", "New Process ID", "New Account ID", "Logged on user", "Home Directory", "Target Domain", "Old UAC Value", "Object Server", "Object Handle", "New UAC Value", "Logon account", "Logon Process", "DNS Host Name", "Client Domain", "Caller Domain", "Shutdown Type", "Service Name", "Profile Path", "Failure Code", "Display Name", "Account Name", "User Domain", "Status code", "Source Port", "Sid History", "Script Path", "Result Code", "Object Type", "Object Name", "Member Name", "Logon Hours", "Access Mask", "Reason Code", "Service ID", "Properties", "Process ID", "Privileges", "New Domain", "Logon Type", "Logon GUID", "Home Drive", "Error Code", "Attributes", "User Name", "Member ID", "Handle ID", "Logon ID", "Accesses", "User ID", "Comment", "Windows ", "Reason", "Domain" ) def parseOptions(): options = {'verbose': False, 'errorsonly': False, 'descriptiononly':False, 'describe':True, 'oneline': False } helpstr = 'Usage: ' + sys.argv[0] + ' [OPTIONS]' + """\n Options: -h, --help -e, --errorsonly only show errors -d, --descriptiononly only parse and show the event description, not the details (usefull for counts) -v, --verbose be verbose, include entries whose values are empty or - -o, --oneline print entry as one line instead of header line and tabbed data (Also forces key=value format) \n """ optlist, args = getopt.getopt(sys.argv[1:], 'vhedo', ['help','verbose','errorsonly','descriptiononly','oneline']) #parse options. for o, a in optlist: if (o == '-h' or o == '--help'): print helpstr sys.exit() elif (o == '-v' or o == '--verbose'): options['verbose']=True elif (o=='-e' or o=='--errorsonly' ): options['errorsonly']=True elif (o=='-d' or o=='--descriptiononly' ): options['descriptiononly']=True elif (o=='-o' or o=='--oneline' ): options['oneline']=True else: for option in options.keys(): execcode = "if (o == '-%s' or o == '--%s'): options['%s'] = a" % (option[0], option, option) exec execcode return options def parseandprint(line): #Expects lines in the kiwisyslog format: #Date TimeSyslogFacility.SyslogPrioritysystemnameEventLogType: Account: EventDescription: Freeform Data with messy colons and stuff... try: entrytime=line.split('\t')[0] system=line.split('\t')[2] logtype=string.replace(line.split()[4],':','') #log source (system, security, etc) entry=str(line.split('\t')[3]).strip() + ":" #Add a colon since that's what we delimit fields with #entry=entry.replace("C:\\","\\") #filenames have colons! source=str(entry.split(':')[1]).strip() eventdesc=str(entry.split(':')[2]).strip() if options['descriptiononly']: print eventdesc else: #print entry soutput="" if options['oneline']: soutput=entrytime + " system=\"" + system + "\" logtype=\"" + logtype + "\" description=\"" + eventdesc + "\" source=\"" + source + "\"" else: soutput=entrytime + " " + system + "/" + logtype + "/" + eventdesc + "/" + source #There are a million different fields in the 'free form' text of an event #The list of ones we know is above in the descriptionKeys #This code loops through looking for all of them to parse them out of the data #for each of the items we think may be in this entry for searchkey in descriptionKeys: #search for it followed by a colon, then anything then another colon example: Process ID:944 Domain:MyDomain #This scheme doesn't work for time values because a colon is a stupid delimter MSoft! #So the workaround is to look for time entries explicitly if (searchkey in ("Password Last Set","Account Expires")): #Time's have multiple colons since time has HH:MM:SS AM/PM #This probably only works for my time format mm/dd/yyyy #One colon followed by space, followed by mm/dd/yyyy AM/PM then a space p=re.compile(searchkey + ":{1} [0-9]{1,2}/[0-9]{1,2}/[0-9]{2,4}.*[AM|PM] ") elif (searchkey in ("Image File Name")): #file name's often include the drive letter c:\program files\blahdeblah and is followed by 'creator process id' p=re.compile(searchkey + r": [A-Za-z]{0,1}:\\.*Creator Process ID|: [A-Za-z]{0,1}:\\.*Primary User Name") m=p.search(entry) if not m: p=re.compile(searchkey + r": \\.*Creator Process ID|: \\.*Primary User Name") else: p=re.compile(searchkey + ":.*:") m=p.search(entry) if m: if (searchkey in ("Password Last Set","Account Expires","Image File Name")): #for dates, grab everything after the first colon, expect other colons svalue=str(m.group().split(searchkey+ ":")[1]) else: #The value is the thing after the colon svalue = str(m.group().split(":")[1]) #But the value probably also has another key in it example 944 Domain: #So run through the list of keys we know about and take 'em all out if they're there for key in descriptionKeys: svalue = svalue.replace(key,"").strip() #result, is search key and value. #replace codes with descriptions? if options['describe']: if (searchkey=="Failure Code"): if KerberosFailureCodes.has_key(svalue): svalue = svalue + " " + KerberosFailureCodes[svalue] if len(svalue)==0 or svalue=='-' : if options['verbose'] : if options['oneline']: soutput+= " " + searchkey + "=" + svalue else: soutput+="\n\t\t\t\t\t\t\t\t" + searchkey + ":" + svalue else: if options['oneline']: soutput+= " " + string.replace(searchkey,' ','') + "=\"" + svalue + "\"" else: soutput+="\n\t\t\t\t\t\t\t\t" + searchkey + ":" + svalue soutput+="\n" sys.stdout.write(soutput) sys.stdout.flush() #os.close(1) except IndexError: sys.stderr.write("Index Error on:" + line + "\n") def main(): global options options=parseOptions() #kiwi syslog/evtsys combination reports windows event log errors as "Daemon.Error" in syslog parlance. errorre=re.compile('Daemon.Error') while 1: line = sys.stdin.readline().strip() if not line: break else: #anything dropped errorinline=errorre.findall(line) if options['errorsonly'] & len(line) > 0: if len(errorinline)>0: parseandprint(line) elif len(line)>0: parseandprint(line) main() #todo: #target account: domain admins domain:name gets muffed when it pulls domain out of 'domain admins' #fix parsing of lines like: #2006-07-24 16:16:27 Daemon.Notice ipwtcdc1 Application Popup: N/A: Application popup: Windows : Other people are logged on to this computer. Restarting Windows might cause them to lose data. Do you want to continue restarting?