SaltyCrane Blog — Notes on JavaScript and web development

Larger python qt pyqt example

This script is used to parse Windows Event Viewer logs. It uses dumpel.exe. It colors significant events and presents results in a QT GUI.
"""windows_audit.py
"""

__author__ = "So Feng"
__version__ = "$Revision: 1.0 $"
__date__ = "$Date: 2006/10/17 $"
__license__ = "Python"

import re
import datetime
from datetime import date
import time
import glob
import os
import sys
from Qt import *
import StringIO

# constants
NCHARS_TO_WRAP = 30
EARLY_HOUR = 6
LATE_HOUR = 20
outfile = "audit_" + str(datetime.date.today()) + ".txt"
red_eventids = ["560","565","592","678"] orange_eventids = ["539","629","644","531","544","545","675","676"]
yellow_eventids = ["529","530","532","533","534","535","536","537","681",
                   "576","608","609","610","611","624","625","626","627",
                   "628","630","631","632","633","634","635","636","637",
                   "638","639","640","641","642","643","645","646","647",
                   "648","649","650","651","652","653","654","655","656",
                   "657","658","659","660","661","662","663","664","665",
                   "666","667","668","669","670","672"]
green_eventids = ["528", "538", "540", "680"] other_eventids = ["612", "617", "618", "619"] failure_list = ('529', '530', '531', '532', '533', '534', '535', '536', '537', '539', '544', '545', '546', '547', '616', '675', '676', '677', '679', '681', ) success_list = ('512', '513', '514', '515', '516', '517', '518', '528', '538', '540', '541', '542', '543', '560', '561', '562', '563', '564', '565', '566', '576', '577', '578', '592', '593', '594', '595', '608', '609', '610', '611', '612', '613', '614', '615', '617', '618', '619', '620', '624', '625', '626', '627', '628', '630', '631', '632', '633', '634', '635', '636', '637', '638', '639', '640', '641', '642', '643', '644', '645', '646', '647', '648', '649', '650', '651', '652', '653', '654', '655', '656', '657', '658', '659', '660', '661', '662', '663', '664', '665', '666', '667', '668', '669', '670', '672', '673', '674', '678', '680', '682', '683', ) event_desc = {
'512': 'Windows NT is starting up. ',
'513': 'Windows NT is shutting down.\n             All logon sessions will be terminated by this shutdown. ',
'514': 'An authentication package has been loaded by the Local Security Authority.\n             This authentication package will be used to authenticate logon attempts. ',
'515': 'A trusted logon process has registered with the Local Security Authority.\n             This logon process will be trusted to submit logon requests. ',
'516': 'Internal resources allocated for the queuing of audit messages have been\n             exhausted, leading to the loss of some audits. ',
'517': 'The audit log was cleared ',
'518': 'An notification package has been loaded by the Security Account Manager.\n             This package will be notified of any account or password changes. ',
'528': 'Successful Logon: ',
'529': 'Logon Failure Unknown user name or bad password',
'530': 'Logon Failure Account logon time restriction violation',
'531': 'Logon Failure Account currently disabled',
'532': 'Logon Failure The specified user account has expired',
'533': 'Logon Failure User not allowed to logon at this computer',
'534': 'Logon Failure The user has not been granted the requested \n             logon type at this machine',
'535': 'Logon Failure The specified account\'s password has expired',
'536': 'Logon Failure The NetLogon component is not active',
'537': 'Logon Failure An unexpected error occurred during logon',
'538': 'User Logoff ',

'539': 'Logon Failure Account locked out',
'540': 'Successful Network Logon ',
'541': 'IKE security association established. ',
'542': 'IKE security association ended. ',
'543': 'IKE security association ended. ',
'544': 'IKE security association establishment failed because peer could not\n             authenticate. The certificate trust could not be established. ',
'545': 'IKE peer authentication failed. ',
'546': 'IKE security association establishment failed because peer\n             sent invalid proposal. ',
'547': 'IKE security association negotiation failed. ',
'560': 'Object Open ',
'561': 'Handle Allocated ',
'562': 'Handle Closed ',
'563': 'Object Open for Delete ',
'564': 'Object Deleted ',
'565': 'Object Open ',
'566': 'Object Operation ',
'576': 'Special privileges assigned to new logon: ',
'577': 'Privileged Service Called ',
'578': 'Privileged object operation ',
'592': 'A new process has been created ',
'593': 'A process has exited ',
'594': 'A handle to an object has been duplicated ',
'595': 'Indirect access to an object has been obtained ',
'608': 'User Right Assigned ',
'609': 'User Right Removed ',
'610': 'New Trusted Domain ',
'611': 'Removing Trusted Domain ',
'612': 'Audit Policy Change ',
'613': 'IPSec policy agent started ',
'614': 'IPSec policy agent disabled ',
'615': 'IPSEC PolicyAgent Service:  %1 ',
'616': 'IPSec policy agent encountered a potentially serious failure. ',
'617': 'Kerberos Policy Changed ',
'618': 'Encrypted Data Recovery Policy Changed ',
'619': 'Quality of Service Policy Changed ',
'620': 'Trusted Domain Information Modified: ',
'624': 'User Account Created ',
'625': 'User Account Type Change ',
'626': 'User Account Enabled ',
'627': 'Change Password Attempt ',
'628': 'User Account password set ',
'630': 'User Account Deleted: ',
'631': 'Security Enabled Global Group Created ',
'632': 'Security Enabled Global Group Member Added ',
'633': 'Security Enabled Global Group Member Removed ',
'634': 'Security Enabled Global Group Deleted ',
'635': 'Security Enabled Local Group Created ',
'636': 'Security Enabled Local Group Member Added ',
'637': 'Security Enabled Local Group Member Removed ',
'638': 'Security Enabled Local Group Deleted ',
'639': 'Security Enabled Local Group Changed ',
'640': 'General Account Database Change ',
'641': 'Security Enabled Global Group Changed ',
'642': 'User Account Changed ',
'643': 'Domain Policy Changed: %1 modified ',
'644': 'User Account Locked Out ',
'645': 'Computer Account Created ',
'646': 'Computer Account Changed ',
'647': 'Computer Account Deleted ',
'648': 'Security Disabled Local Group Created ',
'649': 'Security Disabled Local Group Changed ',
'650': 'Security Disabled Local Group Member Added ',
'651': 'Security Disabled Local Group Member Removed ',
'652': 'Security Disabled Local Group Deleted ',
'653': 'Security Disabled Global Group Created ',
'654': 'Security Disabled Global Group Changed ',
'655': 'Security Disabled Global Group Member Added ',
'656': 'Security Disabled Global Group Member Removed ',
'657': 'Security Disabled Global Group Deleted ',
'658': 'Security Enabled Universal Group Created ',
'659': 'Security Enabled Universal Group Changed ',
'660': 'Security Enabled Universal Group Member Added ',
'661': 'Security Enabled Universal Group Member Removed ',
'662': 'Security Enabled Universal Group Deleted ',
'663': 'Security Disabled Universal Group Created ',
'664': 'Security Disabled Universal Group Changed ',
'665': 'Security Disabled Universal Group Member Added ',
'666': 'Security Disabled Universal Group Member Removed ',
'667': 'Security Disabled Universal Group Deleted ',
'668': 'Group Type Changed ',
'669': 'Add SID History ',
'670': 'Add SID History ',
'672': 'Authentication Ticket Granted ',
'673': 'Service Ticket Granted ',
'674': 'Ticket Granted Renewed ',
'675': 'Pre-authentication failed ',
'676': 'Authentication Ticket Request Failed ',
'677': 'Authentication Ticket Request Failed ',
'678': 'Account Mapped for Logon by: %1 ',
'679': 'The name: %2 could not be mapped for logon by: %1 ',
'680': 'Account Used for Logon by: %1 ',
'681': 'The logon to account: %2 by: %1 from workstation: %3 failed. The error code was: %4 ',
'682': 'Session reconnected to winstation: ',
'683': 'Session disconnected from winstation: ',
              }
#sys.stderr = StringIO.StringIO()
#sys.stdout = StringIO.StringIO()
################################################################            
################################################################
def main():
    app = QApplication(sys.argv)
    app.setQuitOnLastWindowClosed(True)
    form = StartForm()
    form.show()
    report = ReportWindow()
    app.connect(form, SIGNAL("okClicked"),
                report.create)
    sys.exit(app.exec_())
    
################################################################            
################################################################
class StartForm(QWidget):
    def __init__(self, *args):
        QWidget.__init__(self, *args)
        
#        # directory tree
#        label_file = QLabel()
#        label_file.setText("Select file:")
#        dirmodel = QDirModel()
#        treeview = QTreeView(self)
#        treeview.setModel(dirmodel)
#        treeview.setRootIndex(dirmodel.index(QDir.currentPath()))
#        treeview.hideColumn(1)
#        treeview.hideColumn(2)
#        treeview.hideColumn(3)
#        treeview.header().hide()
        
        # date box
        self.label_date = QLabel()
        self.label_date.setText("Set date of last audit:")
        default = datetime.date.today() - datetime.timedelta(10)
        self.datebox = QDateEdit(QDate(default.year, default.month, default.day))
        
        # buttons
        spacer = QSpacerItem(20,40,QSizePolicy.Minimum,QSizePolicy.Expanding)
        self.button_ok = QPushButton()
        self.button_ok.setText("OK")
        self.button_ok.setDefault(True)
        button_cancel = QPushButton()
        button_cancel.setText("Cancel")
    
        # layout
#        layout_left = QVBoxLayout()
#        layout_left.addWidget(label_file)
#        layout_left.addWidget(treeview)
        layout_right = QVBoxLayout(self)
        layout_right.addWidget(self.label_date)
        layout_right.addWidget(self.datebox)
        layout_right.addItem(spacer)
        layout_right.addWidget(self.button_ok)
        layout_right.addWidget(button_cancel)
#        layout = QHBoxLayout(self)
#        layout.addLayout(layout_left)
#        layout.addLayout(layout_right)
        
        # connections
        self.connect(button_cancel, SIGNAL("clicked()"),
                    self.close)
        self.connect(self.button_ok, SIGNAL("clicked()"),
                    self.ok_clicked)
        
    def ok_clicked(self):
        self.label_date.setText("Getting eventlog data...")
        year = self.datebox.date().year()
        month = self.datebox.date().month()
        day = self.datebox.date().day()
        delta = datetime.date.today() - datetime.date(int(year),int(month),int(day))
        self.emit(SIGNAL("okClicked"), delta.days)
        self.close()
        
################################################################            
################################################################
class ReportWindow(QMainWindow):
    def __init__(self, *args):
        QMainWindow.__init__(self, *args)
#        self.cb = QCheckBox()
        self.table = MyTable()
        self.textbrowser = QTextBrowser()
        self.textbrowser.setFontFamily("Courier")
        self.textbrowser.setFontPointSize(10)
        splitter = QSplitter(Qt.Vertical, self)
#        splitter = QSplitter(Qt.Vertical)
        splitter.addWidget(self.table)
        splitter.addWidget(self.textbrowser)
#        vlayout = QVBoxLayout(self)
#        vlayout.addWidget(self.cb)
#        vlayout.addWidget(splitter)        
        self.setGeometry(100,100,750,550)
        self.setCentralWidget(splitter)
        
    def create(self, date):
        self.table.parse_event_log(date) 
        self.table.setdata()

        fh = open(outfile, "r")
        text = fh.read()
        self.textbrowser.setPlainText(text)
        
        self.show()

################################################################
################################################################
class MyTable(QTableWidget):
    def __init__(self, *args):
        QTableWidget.__init__(self, *args)
        self.setSortingEnabled(True)
        self.setSelectionMode(self.ContiguousSelection)
        #self.setFixedWidth(750)
        self.setGeometry(0,0,700,400)
        self.data = []
    
    def setdata(self):
        if len(self.data) == 0:
            self.setRowCount(1)
            self.setColumnCount(1)
            newitem = QTableWidgetItem("No data for this date range.")
            self.setItem(0, 0, newitem)
            self.resizeColumnsToContents()
        else:
            self.nrows = len(self.data)
            self.ncols = len(self.data[0])
            self.setRowCount(self.nrows)
            self.setColumnCount(self.ncols)
            self.setmydata_list()
            self.resizeColumnsToContents()
            self.setGridStyle(Qt.DotLine)
            self.setShowGrid(False)
            self.setColumnWidth(3, 250)
            #self.resizeRowsToContents()
            #self.setHorizontalHeaderLabels(['date','time','?','?','event','source','user','?','computer','data'])
            self.setHorizontalHeaderLabels(['Date','Time','Type','Event','User','Computer','Data'])

    def setmydata_list(self):    
        n = 0
        date_prev = ''
        for row in self.data:
            # event processing
            event = row[4]
            try:
                row[4] = event + ": " + event_desc[event]
            except:
                pass
            color = QColor("white")
            if event in red_eventids:
                color = QColor("red")
            elif event in orange_eventids:
                color = QColor("orange")
            elif event in yellow_eventids:
                color = QColor("yellow")
            elif event in green_eventids:
                #color = QColor("green")
                pass
            elif event in other_eventids:
                color = QColor("blue")
            
            # success or failure processing
            if row[2] == "8":
                row[2] = "Success"
                icon = QIcon("success.png")
            elif row[2] == "16":
                row[2] = "Failure"
                icon = QIcon("failure.png")
            else:
                row[2] = "Unknown"
                icon = QIcon("unknown.png")
                
            # date processing
            row[0] = format_datetime(row[0], row[1])
            (date, thetime) = re.split(r"\n", row[0])
            newday = False
            if date != date_prev:
                newday = True
            date_prev = date
            thetime = re.split(r":", thetime)
            hour = int(thetime[0])
            if hour <= EARLY_HOUR or hour >= LATE_HOUR:
                colordate = QColor(0,0,102)
            else:
                colordate = QColor("white")

            # user
            (dom, user) = re.split(r"\\", row[6])
            row[6] = user
            if user in ("SYSTEM", "NETWORK SERVICE", "LOCAL SERVICE", "ANONYMOUS LOGON"):
                textcolor = QColor("gray")
                font = QFont("Arial", 8)
            else:
                textcolor = QColor("black")
                font = QFont("Arial", 8)
                font.setBold(True)

            # insert line
            if newday:
                m = 0
                for j in range(len(row)-3):
                    text = ""
                    if j == 0:
                        text = date
                    newitem = QTableWidgetItem(text)
                    newitem.setBackgroundColor(QColor("black"))
                    self.setItem(n, m, newitem)
                    m += 1
                self.setRowHeight(n, 15)
                n += 1

            #for item in row:
            m = 0
            for j in range(len(row)):
                # skip these columns
                if j in (3,5,7):
                    continue
                
                item = row[j]
                if j == 1:
                    color2 = colordate
                else:
                    color2 = color

                newitem = QTableWidgetItem(item)
                newitem.setBackgroundColor(color2)
                newitem.setTextColor(textcolor)
                newitem.setFont(font)
                newitem.setTextAlignment(Qt.AlignTop)
                newitem.setToolTip(item)
                if j == 2:
                    newitem.setIcon(icon)
                self.setItem(n, m, newitem)
                m += 1
            self.setRowHeight(n, 16)
            n += 1

    def parse_event_log(self, ndays):
        """Parses event log file.
        Returns none.
        """
        # run dumpel.exe
        os.system("dumpel -f dumpel_results.txt -l security -d %s" % ndays)
        fin = open("dumpel_results.txt")
        lines = fin.readlines()
    
        # open output files
        #outfile = "audit_" + str(datetime.date.today()) + ".txt"
        fout=open(outfile,'w')
    
        # initialization
        fout.write("LOGON/LOGOFFS:\n")
        fout.write("--------------\n")
        output = "%-13s %3s %11s %12s %12s\n" % ('user','day','date','logon','logoff')
        fout.write(output)
        event578_count = 0
        user_logged_in = 0
        red_events = []
        orange_events = []
        yellow_events = []
        
        # loop on each line in the file
        for line in lines:
            items = re.split('\t', line)
            self.data.append(items)
            date = items[0]
            time = items[1]
            event = items[4]
            (dom, user) = re.split(r"\\", items[6])
            day = get_weekday(date)
            if event in red_eventids:
                red_events.append(line)
            elif event in orange_eventids:
                orange_events.append(line)
            elif event in yellow_eventids:
                yellow_events.append(line)
            elif user != "SYSTEM":
                #if (not user_logged_in) and (event == "528"):
                if event == "528":
                    if user_logged_in:
                        output = "%-13s %3s %11s %12s %12s\n" % (user,day,logon_date,logon_time,'no logout')
                        fout.write(output)
                    logon_time = time
                    logon_user = user
                    logon_date = date
                    logout_backup = ""
                    user_logged_in = 1
                        
                elif user_logged_in and event == "578":
                    if user == logon_user:
                        if event578_count == 0:
                            event578_count = 1
                        elif event578_count == 1:
                            event578_count = 0
                            user_logged_in = 0
                            output = "%-13s %3s %11s %12s %12s\n" % (user,day,logon_date,logon_time,time)
                            fout.write(output)
    
        # print red events
        fout.write("\nRED EVENTS:\n")
        fout.write("-----------\n")
        if len(red_events) == 0:
            fout.write("None.\n")
        else:
            for event in red_events:
                fout.write(event)
    
        # print orange events
        fout.write("\nORANGE EVENTS:\n")
        fout.write("--------------\n")
        if len(orange_events) == 0:
            fout.write("None.\n")
        else:
            for event in orange_events:
                fout.write(event)
    
        # print yellow events
        fout.write("\nYELLOW EVENTS:\n")
        fout.write("--------------\n")
        if len(yellow_events) == 0:
            fout.write("None.\n")
        else:
            for event in yellow_events:
                fout.write(event)
    
        # close files
        fin.close()
        fout.close()
    
        # print message
        print "Parse sucessful.\n"
        
        return

################################################################
################################################################
def get_weekday(date):
    (month,day,year) = re.split('/', date)
    weekday = datetime.date(int(year),int(month),int(day)).weekday()
    day_names = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun']

    return day_names[weekday]

################################################################
################################################################
def format_datetime(date, thetime):
    (month,day,year) = re.split('/', date)
    dateobj = datetime.date(int(year),int(month),int(day))
    newdate = "%04d/%02d/%02d" % (dateobj.year, dateobj.month, dateobj.day)
    
    timeobj = time.strptime(thetime, "%I:%M:%S %p")
    newtime = time.strftime("%H:%M:%S", timeobj)
    
    return newdate + "\n" + newtime

################################################################
################################################################
def get_dateobject(date):
    (month,day,year) = re.split('/', date)
    return datetime.date(int(year),int(month),int(day))
  
################################################################
################################################################
if __name__ == "__main__":
    main()

Comments


#1 Anonymous commented on :

Hello
Thanks for your postings.

What is the best way to deal with null dates in a QDateEdit? - ie the end date for a particular date range that hasn't ended yet? I want the user to set the date and not have a date equivalent of null displayed.