Merge branch 'master' into patch-1

This commit is contained in:
Matteo ℱan 2020-02-11 23:10:17 +01:00 committed by GitHub
commit b878604c4c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 2459 additions and 595 deletions

4
.gitignore vendored
View File

@ -1,5 +1,9 @@
# App files
pykms_logserver.log*
pykms_logclient.log*
pykms_newlines.txt*
pykms_config.pickle*
etrigan.log*
# Byte-compiled / optimized / DLL files
__pycache__/

View File

@ -1,3 +1,10 @@
### py-kms_2020-02-02
- Optimized pretty-print messages process.
- Added -F FILESTDOUT option.
- Added deamonization options (via [Etrigan](https://github.com/SystemRage/Etrigan) project).
- py-kms GUI resurrected (and improved).
- Cleaned, cleaned, cleaned.
### py-kms_2019-05-15
- Merging for Python2 / Python3 compatibility all-in-one.
- Added new options:

View File

@ -8,7 +8,7 @@ _py-kms_ is a port of node-kms created by [cyrozap](http://forums.mydigitallife.
- Windows 7
- Windows 8
- Windows 8.1
- Windows 10 ( 1511 / 1607 / 1703 / 1709 / 1803 / 1809 / 1903 )
- Windows 10 ( 1511 / 1607 / 1703 / 1709 / 1803 / 1809 / 1903 / 1909 )
- Windows Server 2008
- Windows Server 2008 R2
- Windows Server 2012
@ -39,16 +39,19 @@ _py-kms_ is a port of node-kms created by [cyrozap](http://forums.mydigitallife.
# Usage
- __NOTE__: Pay attention to how invoke scripts, if you want to run with python2 use ```python...``` while for python3 use ```python3...```, also depending on the Python versions that resides in your PC.
- To start the server, execute ```python pykms_Server.py [IPADDRESS] [PORT]```.
The default _IPADDRESS_ is "0.0.0.0" ( all interfaces ) and the default _PORT_ is "1688".
- To run the client (only for testing purposes), use ```python pykms_Client.py IPADDRESS [PORT]```.
Argument _IPADDRESS_ is always required, while the default _PORT_ is "1688", so a valid command is: ```python pykms_Client.py 0.0.0.0```
- To show the help pages type: ```python pykms_Server.py -h``` and ```python pykms_Client.py -h```
- To generate a random HWID use ```-w``` option: ```python pykms_Server.py -w RANDOM```
- To get the HWID from any server use the client, for example type: ```python pykms_Client.py 0.0.0.0 1688 -m Windows8.1 -V INFO```
- To view a minimal set of logging information use ```-V MINI``` option, for example: ```python pykms_Server.py -V MINI```
- To redirect logging on stdout use ```-F STDOUT``` option, for example: ```python pykms_Server.py -F STDOUT```
- For launching py-kms GUI make executable all _.py_ files in _py-kms_ directory ```chmod +x /path/to/scripts/py-kms/*.py```, then simply run ```pykms_Server.py``` double-clicking.
- To start the server, execute ```python pykms_Server.py [IPADDRESS] [PORT]```, the default _IPADDRESS_ is "0.0.0.0" ( all interfaces ) and the default _PORT_ is "1688".
- To run the client (only for testing purposes), use ```python pykms_Client.py [IPADDRESS] [PORT]```, with the same defaults of ```pykms_Server.py```.
- To show the help pages type: ```python pykms_Server.py -h``` and ```python pykms_Client.py -h```.
- To generate a random HWID use ```-w``` option: ```python pykms_Server.py -w RANDOM```.
- To get the HWID from any server use the client, for example type: ```python pykms_Client.py 0.0.0.0 1688 -m Windows8.1 -V INFO```.
- To view a minimal set of logging information use ```-V MINI``` option, for example: ```python pykms_Server.py -F /path/to/your/logfile.log -V MINI```.
- To redirect logging on stdout use ```-F STDOUT``` option, for example: ```python pykms_Server.py -F STDOUT -V DEBUG```.
- You can create logfile and view logging information on stdout at the same time with ```-F FILESTDOUT``` option, for example: ```python pykms_Server.py -F FILESTDOUT /path/to/your/logfile.log -V DEBUG```.
- Select timeout (seconds) for py-kms with ```-t``` option, for example ```python pykms_Server.py -t 10```
- For launching py-kms GUI make executable ```pykms_Server.py``` file with ```chmod +x /path/to/folder/py-kms/pykms_Server.py```, then simply run ```pykms_Server.py``` double-clicking.
- You can run py-kms deamonized (via [Etrigan](https://github.com/SystemRage/Etrigan)) using a command like: ```python pykms_Server.py etrigan start``` and stop it with: ```python pykms_Server.py etrigan stop```.
- With Etrigan you have another way to launch py-kms GUI (specially suitable if you're using a virtualenv), so: ```python pykms_Server.py etrigan start -g```
and stop the GUI with the same precedent command (or interact with EXIT button).
# Other Important Stuff
Consult the [Wiki](https://github.com/SystemRage/py-kms/wiki) for more information about activation with _py-kms_ and to get GVLK keys.

View File

@ -25,7 +25,7 @@ _py-kms_ is a port of node-kms created by [cyrozap](http://forums.mydigitallife.
# Usage
```
docker run -d --name py3-kms \
docker run -it -d --name py3-kms \
-p 8080:8080 \
-p 1688:1688 \
-e IP=0.0.0.0 \

View File

@ -50,7 +50,7 @@ ENV SQLITE false
# EN: hwid
# RU: hwid
ENV HWID 364F463A8863D35F
ENV HWID "364F463A8863D35F"
# Use this flag to specify a HWID.
# The HWID must be an 16-character string of hex characters.
# The default is "364F463A8863D35F" or type "RANDOM" to auto generate the HWID.
@ -107,7 +107,7 @@ chmod a+x /usr/bin/start.sh && \
# EN: Clear after install software
# RU: Очистка после установки программного обеспечения
apk del git py2-pip
apk del git
# Set Workdir
WORKDIR /home/py-kms

View File

@ -27,25 +27,25 @@ else
then
/bin/bash -c "/usr/bin/python pykms_Server.py ${IP} ${PORT} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -s -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE} &"
sleep 5
/usr/bin/python pykms_Client.py ${IP} ${PORT} -m Windows10 &
/usr/bin/python pykms_Client.py ${IP} ${PORT} -m Windows7 &
/usr/bin/python /home/sqlite_web/sqlite_web.py -H ${IP} -x ${PWD}/clients.db
else
/bin/bash -c "/usr/bin/python pykms_Server.py ${IP} ${PORT} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -s -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE} -S ${LOGSIZE} &"
sleep 5
/usr/bin/python pykms_Client.py ${IP} ${PORT} -m Windows10 &
/usr/bin/python pykms_Client.py ${IP} ${PORT} -m Windows7 &
/usr/bin/python /home/sqlite_web/sqlite_web.py -H ${IP} -x ${PWD}/clients.db
fi
else
if [ "$LOGSIZE" == "" ];
then
/bin/bash -c "/usr/bin/python pykms_Server.py ${IP} ${PORT} -e ${EPID} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -s -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE} &"
sleep5
/usr/bin/python pykms_Client.py ${IP} ${PORT} -m Windows10 &
sleep 5
/usr/bin/python pykms_Client.py ${IP} ${PORT} -m Windows7 &
/usr/bin/python /home/sqlite_web/sqlite_web.py -H ${IP} -x ${PWD}/clients.db
else
/bin/sh -c "/usr/bin/python pykms_Server.py ${IP} ${PORT} -e ${EPID} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -s -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE} -S ${LOGSIZE} &"
sleep 5
/usr/bin/python pykms_Client.py ${IP} ${PORT} -m Windows10 &
/usr/bin/python pykms_Client.py ${IP} ${PORT} -m Windows7 &
/usr/bin/python /home/sqlite_web/sqlite_web.py -H ${IP} -x ${PWD}/clients.db
fi
fi

View File

@ -14,4 +14,3 @@ docker run -d --name py3-kms \
-v /etc/localtime:/etc/localtime:ro \
-v /var/log:/var/log:rw \
--restart unless-stopped pykms/pykms:py3-kms
# --restart unless-stopped ekonprof18/pykms:py3-kms

View File

@ -39,7 +39,7 @@ else
if [ "$LOGSIZE" == "" ];
then
/bin/bash -c "/usr/bin/python3 pykms_Server.py ${IP} ${PORT} -e ${EPID} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -s -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE} &"
sleep5
sleep 5
/usr/bin/python3 pykms_Client.py ${IP} ${PORT} -m Windows10 &
/usr/bin/python3 /home/sqlite_web/sqlite_web.py -H ${IP} -x ${PWD}/clients.db --read-only
else

610
py-kms/Etrigan.py Normal file
View File

@ -0,0 +1,610 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import atexit
import errno
import os
import sys
import time
import signal
import logging
import argparse
from collections import Sequence
__version__ = "0.1"
__license__ = "MIT License"
__author__ = u"Matteo an <SystemRage@protonmail.com>"
__copyright__ = "© Copyright 2020"
__url__ = "https://github.com/SystemRage/Etrigan"
__description__ = "Etrigan: a python daemonizer that rocks."
path = os.path.dirname(os.path.abspath(__file__))
class Etrigan(object):
"""
Daemonizer based on double-fork method
--------------------------------------
Each option can be passed as a keyword argument or modified by assigning
to an attribute on the instance:
jasonblood = Etrigan(pidfile,
argument_example_1 = foo,
argument_example_2 = bar)
that is equivalent to:
jasonblood = Etrigan(pidfile)
jasonblood.argument_example_1 = foo
jasonblood.argument_example_2 = bar
Object constructor expects always `pidfile` argument.
`pidfile`
Path to the pidfile.
The following other options are defined:
`stdin`
`stdout`
`stderr`
:Default: `os.devnull`
File objects used as the new file for the standard I/O streams
`sys.stdin`, `sys.stdout`, and `sys.stderr` respectively.
`funcs_to_daemonize`
:Default: `[]`
Define a list of your custom functions
which will be executed after daemonization.
If None, you have to subclass Etrigan `run` method.
Note that these functions can return elements that will be
added to Etrigan object (`etrigan_add` list) so the other subsequent
ones can reuse them for further processing.
You only have to provide indexes of `etrigan_add` list,
(an int (example: 2) for single index or a string (example: '1:4') for slices)
as first returning element.
`want_quit`
:Default: `False`
If `True`, runs Etrigan `quit_on_start` or `quit_on_stop`
lists of your custom functions at the end of `start` or `stop` operations.
These can return elements as `funcs_to_daemonize`.
`logfile`
:Default: `None`
Path to the output log file.
`loglevel`
:Default: `None`
Set the log level of logging messages.
`mute`
:Default: `False`
Disable all stdout and stderr messages (before double forking).
`pause_loop`
:Default: `None`
Seconds of pause between the calling, in an infinite loop,
of every function in `funcs_to_daemonize` list.
If `-1`, no pause between the calling, in an infinite loop,
of every function in `funcs_to_daemonize` list.
If `None`, only one run (no infinite loop) of functions in
`funcs_to_daemonize` list, without pause.
"""
def __init__(self, pidfile,
stdin = os.devnull, stdout = os.devnull, stderr = os.devnull,
funcs_to_daemonize = [], want_quit = False,
logfile = None, loglevel = None,
mute = False, pause_loop = None):
self.pidfile = pidfile
self.funcs_to_daemonize = funcs_to_daemonize
self.stdin = stdin
self.stdout = stdout
self.stderr = stderr
self.logfile = logfile
self.loglevel = loglevel
self.mute = mute
self.want_quit = want_quit
self.pause_loop = pause_loop
# internal only.
self.homedir = '/'
self.umask = 0o22
self.etrigan_restart, self.etrigan_reload = (False for _ in range(2))
self.etrigan_alive = True
self.etrigan_add = []
self.etrigan_index = None
# seconds of pause between stop and start during the restart of the daemon.
self.pause_restart = 5
# when terminate a process, seconds to wait until kill the process with signal.
# self.pause_kill = 3
# create logfile.
self.setup_files()
def handle_terminate(self, signum, frame):
if os.path.exists(self.pidfile):
self.etrigan_alive = False
# eventually run quit (on stop) function/s.
if self.want_quit:
if not isinstance(self.quit_on_stop, (list, tuple)):
self.quit_on_stop = [self.quit_on_stop]
self.execute(self.quit_on_stop)
# then always run quit standard.
self.quit_standard()
else:
self.view(self.logdaemon.error, self.emit_error, "Failed to stop the daemon process: can't find PIDFILE '%s'" %self.pidfile)
sys.exit(0)
def handle_reload(self, signum, frame):
self.etrigan_reload = True
def setup_files(self):
self.pidfile = os.path.abspath(self.pidfile)
if self.logfile is not None:
self.logdaemon = logging.getLogger('logdaemon')
self.logdaemon.setLevel(self.loglevel)
filehandler = logging.FileHandler(self.logfile)
filehandler.setLevel(self.loglevel)
formatter = logging.Formatter(fmt = '[%(asctime)s] [%(levelname)8s] --- %(message)s',
datefmt = '%Y-%m-%d %H:%M:%S')
filehandler.setFormatter(formatter)
self.logdaemon.addHandler(filehandler)
else:
nullhandler = logging.NullHandler()
self.logdaemon.addHandler(nullhandler)
def emit_error(self, message, to_exit = True):
""" Print an error message to STDERR. """
if not self.mute:
sys.stderr.write(message + '\n')
sys.stderr.flush()
if to_exit:
sys.exit(1)
def emit_message(self, message, to_exit = False):
""" Print a message to STDOUT. """
if not self.mute:
sys.stdout.write(message + '\n')
sys.stdout.flush()
if to_exit:
sys.exit(0)
def view(self, logobj, emitobj, msg, **kwargs):
options = {'to_exit' : False,
'silent' : False
}
options.update(kwargs)
if logobj:
logobj(msg)
if emitobj:
if not options['silent']:
emitobj(msg, to_exit = options['to_exit'])
def daemonize(self):
"""
Double-forks the process to daemonize the script.
see Stevens' "Advanced Programming in the UNIX Environment" for details (ISBN 0201563177)
http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
"""
self.view(self.logdaemon.debug, None, "Attempting to daemonize the process...")
# First fork.
self.fork(msg = "First fork")
# Decouple from parent environment.
self.detach()
# Second fork.
self.fork(msg = "Second fork")
# Write the PID file.
self.create_pidfile()
self.view(self.logdaemon.info, self.emit_message, "The daemon process has started.")
# Redirect standard file descriptors.
sys.stdout.flush()
sys.stderr.flush()
self.attach('stdin', mode = 'r')
self.attach('stdout', mode = 'a+')
try:
self.attach('stderr', mode = 'a+', buffering = 0)
except ValueError:
# Python 3 can't have unbuffered text I/O.
self.attach('stderr', mode = 'a+', buffering = 1)
# Handle signals.
signal.signal(signal.SIGINT, self.handle_terminate)
signal.signal(signal.SIGTERM, self.handle_terminate)
signal.signal(signal.SIGHUP, self.handle_reload)
#signal.signal(signal.SIGKILL....)
def fork(self, msg):
try:
pid = os.fork()
if pid > 0:
self.view(self.logdaemon.debug, None, msg + " success with PID %d." %pid)
# Exit from parent.
sys.exit(0)
except Exception as e:
msg += " failed: %s." %str(e)
self.view(self.logdaemon.error, self.emit_error, msg)
def detach(self):
# cd to root for a guarenteed working dir.
try:
os.chdir(self.homedir)
except Exception as e:
msg = "Unable to change working directory: %s." %str(e)
self.view(self.logdaemon.error, self.emit_error, msg)
# clear the session id to clear the controlling tty.
pid = os.setsid()
if pid == -1:
sys.exit(1)
# set the umask so we have access to all files created by the daemon.
try:
os.umask(self.umask)
except Exception as e:
msg = "Unable to change file creation mask: %s." %str(e)
self.view(self.logdaemon.error, self.emit_error, msg)
def attach(self, name, mode, buffering = -1):
with open(getattr(self, name), mode, buffering) as stream:
os.dup2(stream.fileno(), getattr(sys, name).fileno())
def checkfile(self, path, typearg, typefile):
filename = os.path.basename(path)
pathname = os.path.dirname(path)
if not os.path.isdir(pathname):
msg = "argument %s: invalid directory: '%s'. Exiting..." %(typearg, pathname)
self.view(self.logdaemon.error, self.emit_error, msg)
elif not filename.lower().endswith(typefile):
msg = "argument %s: not a %s file, invalid extension: '%s'. Exiting..." %(typearg, typefile, filename)
self.view(self.logdaemon.error, self.emit_error, msg)
def create_pidfile(self):
atexit.register(self.delete_pidfile)
pid = os.getpid()
try:
with open(self.pidfile, 'w+') as pf:
pf.write("%s\n" %pid)
self.view(self.logdaemon.debug, None, "PID %d written to '%s'." %(pid, self.pidfile))
except Exception as e:
msg = "Unable to write PID to PIDFILE '%s': %s" %(self.pidfile, str(e))
self.view(self.logdaemon.error, self.emit_error, msg)
def delete_pidfile(self, pid):
# Remove the PID file.
try:
os.remove(self.pidfile)
self.view(self.logdaemon.debug, None, "Removing PIDFILE '%s' with PID %d." %(self.pidfile, pid))
except Exception as e:
if e.errno != errno.ENOENT:
self.view(self.logdaemon.error, self.emit_error, str(e))
def get_pidfile(self):
# Get the PID from the PID file.
if self.pidfile is None:
return None
if not os.path.isfile(self.pidfile):
return None
try:
with open(self.pidfile, 'r') as pf:
pid = int(pf.read().strip())
self.view(self.logdaemon.debug, None, "Found PID %d in PIDFILE '%s'" %(pid, self.pidfile))
except Exception as e:
self.view(self.logdaemon.warning, None, "Empty or broken PIDFILE")
pid = None
def pid_exists(pid):
# psutil _psposix.py.
if pid == 0:
return True
try:
os.kill(pid, 0)
except OSError as e:
if e.errno == errno.ESRCH:
return False
elif e.errno == errno.EPERM:
return True
else:
self.view(self.logdaemon.error, self.emit_error, str(e))
else:
return True
if pid is not None and pid_exists(pid):
return pid
else:
# Remove the stale PID file.
self.delete_pidfile(pid)
return None
def start(self):
""" Start the daemon. """
self.view(self.logdaemon.info, self.emit_message, "Starting the daemon process...", silent = self.etrigan_restart)
# Check for a PID file to see if the Daemon is already running.
pid = self.get_pidfile()
if pid is not None:
msg = "A previous daemon process with PIDFILE '%s' already exists. Daemon already running ?" %self.pidfile
self.view(self.logdaemon.warning, self.emit_error, msg, to_exit = False)
return
# Daemonize the main process.
self.daemonize()
# Start a infinitive loop that periodically runs `funcs_to_daemonize`.
self.loop()
# eventualy run quit (on start) function/s.
if self.want_quit:
if not isinstance(self.quit_on_start, (list, tuple)):
self.quit_on_start = [self.quit_on_start]
self.execute(self.quit_on_start)
def stop(self):
""" Stop the daemon. """
self.view(None, self.emit_message, "Stopping the daemon process...", silent = self.etrigan_restart)
self.logdaemon.disabled = True
pid = self.get_pidfile()
self.logdaemon.disabled = False
if not pid:
# Just to be sure. A ValueError might occur
# if the PIDFILE is empty but does actually exist.
if os.path.exists(self.pidfile):
self.delete_pidfile(pid)
msg = "Can't find the daemon process with PIDFILE '%s'. Daemon not running ?" %self.pidfile
self.view(self.logdaemon.warning, self.emit_error, msg, to_exit = False)
return
# Try to kill the daemon process.
try:
while True:
os.kill(pid, signal.SIGTERM)
time.sleep(0.1)
except Exception as e:
if (e.errno != errno.ESRCH):
self.view(self.logdaemon.error, self.emit_error, "Failed to stop the daemon process: %s" %str(e))
else:
self.view(None, self.emit_message, "The daemon process has ended correctly.", silent = self.etrigan_restart)
def restart(self):
""" Restart the daemon. """
self.view(self.logdaemon.info, self.emit_message, "Restarting the daemon process...")
self.etrigan_restart = True
self.stop()
if self.pause_restart:
time.sleep(self.pause_restart)
self.etrigan_alive = True
self.start()
def reload(self):
pass
def status(self):
""" Get status of the daemon. """
self.view(self.logdaemon.info, self.emit_message, "Viewing the daemon process status...")
if self.pidfile is None:
self.view(self.logdaemon.error, self.emit_error, "Cannot get the status of daemon without PIDFILE.")
pid = self.get_pidfile()
if pid is None:
self.view(self.logdaemon.info, self.emit_message, "The daemon process is not running.", to_exit = True)
else:
try:
with open("/proc/%d/status" %pid, 'r') as pf:
pass
self.view(self.logdaemon.info, self.emit_message, "The daemon process is running.", to_exit = True)
except Exception as e:
msg = "There is not a process with the PIDFILE '%s': %s" %(self.pidfile, str(e))
self.view(self.logdaemon.error, self.emit_error, msg)
def flatten(self, alistoflists, ltypes = Sequence):
# https://stackoverflow.com/questions/2158395/flatten-an-irregular-list-of-lists/2158532#2158532
alistoflists = list(alistoflists)
while alistoflists:
while alistoflists and isinstance(alistoflists[0], ltypes):
alistoflists[0:1] = alistoflists[0]
if alistoflists: yield alistoflists.pop(0)
def exclude(self, func):
from inspect import getargspec
args = getargspec(func)
if callable(func):
try:
args[0].pop(0)
except IndexError:
pass
return args
else:
self.view(self.logdaemon.error, self.emit_error, "Not a function.")
return
def execute(self, some_functions):
returned = None
if isinstance(some_functions, (list, tuple)):
for func in some_functions:
l_req = len(self.exclude(func)[0])
if l_req == 0:
returned = func()
else:
l_add = len(self.etrigan_add)
if l_req > l_add:
self.view(self.logdaemon.error, self.emit_error,
"Can't evaluate function: given %s, required %s." %(l_add, l_req))
return
else:
arguments = self.etrigan_add[self.etrigan_index]
l_args = (len(arguments) if isinstance(arguments, list) else 1)
if (l_args > l_req) or (l_args < l_req):
self.view(self.logdaemon.error, self.emit_error,
"Can't evaluate function: given %s, required %s." %(l_args, l_req))
return
else:
if isinstance(arguments, list):
returned = func(*arguments)
else:
returned = func(arguments)
if returned:
if isinstance(returned, (list, tuple)):
if isinstance(returned[0], int):
self.etrigan_index = returned[0]
else:
self.etrigan_index = slice(*map(int, returned[0].split(':')))
if returned[1:] != []:
self.etrigan_add.append(returned[1:])
self.etrigan_add = list(self.flatten(self.etrigan_add))
else:
self.view(self.logdaemon.error, self.emit_error, "Function should return list or tuple.")
returned = None
else:
if some_functions is None:
self.run()
def loop(self):
try:
if self.pause_loop is None:
# one-shot.
self.execute(self.funcs_to_daemonize)
else:
if self.pause_loop >= 0:
# infinite with pause.
time.sleep(self.pause_loop)
while self.etrigan_alive:
self.execute(self.funcs_to_daemonize)
time.sleep(self.pause_loop)
elif self.pause_loop == -1:
# infinite without pause.
while self.etrigan_alive:
self.execute(self.funcs_to_daemonize)
except Exception as e:
msg = "The daemon process start method failed: %s" %str(e)
self.view(self.logdaemon.error, self.emit_error, msg)
def quit_standard(self):
self.view(self.logdaemon.info, None, "Stopping the daemon process...")
self.delete_pidfile(self.get_pidfile())
self.view(self.logdaemon.info, None, "The daemon process has ended correctly.")
def quit_on_start(self):
"""
Override this method when you subclass Daemon.
"""
self.quit_standard()
def quit_on_stop(self):
"""
Override this method when you subclass Daemon.
"""
pass
def run(self):
"""
Override this method when you subclass Daemon.
It will be called after the process has been
daemonized by start() or restart().
"""
pass
#-----------------------------------------------------------------------------------------------------------------------------------------------------------
class JasonBlood(Etrigan):
def run(self):
jasonblood_func()
def jasonblood_func():
with open(os.path.join(path, 'etrigan_test.txt'), 'a') as file:
file.write("Yarva Demonicus Etrigan " + time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime()) + '\n')
def Etrigan_parser(parser = None):
if parser is None:
# create a new parser.
parser = argparse.ArgumentParser(description = __description__, epilog = __version__)
if not parser.add_help:
# create help argument.
parser.add_argument("-h", "--help", action = "help", help = "show this help message and exit")
# attach to an existent parser.
parser.add_argument("operation", action = "store", choices = ["start", "stop", "restart", "status", "reload"],
help = "Select an operation for daemon.", type = str)
parser.add_argument("--etrigan-pid",
action = "store", dest = "etriganpid", default = "/tmp/etrigan.pid",
help = "Choose a pidfile path. Default is \"/tmp/etrigan.pid\".", type = str) #'/var/run/etrigan.pid'
parser.add_argument("--etrigan-log",
action = "store", dest = "etriganlog", default = os.path.join(path, "etrigan.log"),
help = "Use this option to choose an output log file; for not logging don't select it. Default is \"etrigan.log\".", type = str)
parser.add_argument("--etrigan-lev",
action = "store", dest = "etriganlev", default = "DEBUG",
choices = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"],
help = "Use this option to set a log level. Default is \"DEBUG\".", type = str)
parser.add_argument("--etrigan-mute",
action = "store_const", dest = 'etriganmute', const = True, default = False,
help = "Disable all stdout and stderr messages.")
return parser
class Etrigan_check(object):
def emit_opt_err(self, msg):
print(msg)
sys.exit(1)
def checkfile(self, path, typearg, typefile):
filename, extension = os.path.splitext(path)
pathname = os.path.dirname(path)
if not os.path.isdir(pathname):
msg = "argument `%s`: invalid directory: '%s'. Exiting..." %(typearg, pathname)
self.emit_opt_err(msg)
elif not extension == typefile:
msg = "argument `%s`: not a %s file, invalid extension: '%s'. Exiting..." %(typearg, typefile, extension)
self.emit_opt_err(msg)
def checkfunction(self, funcs, booleans):
if not isinstance(funcs, (list, tuple)):
if funcs is not None:
msg = "argument `funcs_to_daemonize`: provide list, tuple or None"
self.emit_opt_err(msg)
for elem in booleans:
if not type(elem) == bool:
msg = "argument `want_quit`: not a boolean."
self.emit_opt_err(msg)
def Etrigan_job(type_oper, daemon_obj):
Etrigan_check().checkfunction(daemon_obj.funcs_to_daemonize,
[daemon_obj.want_quit])
if type_oper == "start":
daemon_obj.start()
elif type_oper == "stop":
daemon_obj.stop()
elif type_oper == "restart":
daemon_obj.restart()
elif type_oper == "status":
daemon_obj.status()
elif type_oper == "reload":
daemon_obj.reload()
sys.exit(0)
def main():
# Parse arguments.
parser = Etrigan_parser()
args = vars(parser.parse_args())
# Check arguments.
Etrigan_check().checkfile(args['etriganpid'], 'pidfile', '.pid')
Etrigan_check().checkfile(args['etriganlog'], 'pidfile', '.log')
# Setup daemon.
jasonblood_1 = Etrigan(pidfile = args['etriganpid'], logfile = args['etriganlog'], loglevel = args['etriganlev'],
mute = args['etriganmute'],
funcs_to_daemonize = [jasonblood_func], pause_loop = 5)
## jasonblood_2 = JasonBlood(pidfile = args['etriganpid'], logfile = args['etriganlog'], loglevel = args['etriganlev'],
## mute = args['etriganmute'],
## funcs_to_daemonize = None, pause_loop = 5)
# Do job.
Etrigan_job(args['operation'], jasonblood_1)
if __name__ == '__main__':
main()

View File

@ -2,8 +2,6 @@
import binascii
import logging
import os
import sys
import time
import uuid
import socket
@ -13,7 +11,7 @@ from pykms_DB2Dict import kmsDB2Dict
from pykms_PidGenerator import epidGenerator
from pykms_Filetimes import filetime_to_dt
from pykms_Sql import sql_initialize, sql_update, sql_update_epid
from pykms_Format import justify, byterize, enco, deco, ShellMessage
from pykms_Format import justify, byterize, enco, deco, pretty_printer
#--------------------------------------------------------------------------------------------------------------------------------------------------------
@ -112,7 +110,7 @@ class kmsBase:
if self.srv_config['sqlite'] and self.srv_config['dbSupport']:
self.dbName = sql_initialize()
ShellMessage.Process(15).run()
pretty_printer(num_text = 15, where = "srv")
kmsRequest = byterize(kmsRequest)
loggersrv.debug("KMS Request Bytes: \n%s\n" % justify(deco(binascii.b2a_hex(enco(str(kmsRequest), 'latin-1')), 'latin-1')))
loggersrv.debug("KMS Request: \n%s\n" % justify(kmsRequest.dump(print_to_stdout = False)))
@ -130,10 +128,12 @@ class kmsBase:
tz = get_localzone()
local_dt = tz.localize(requestDatetime)
except UnknownTimeZoneError:
loggersrv.warning('Unknown time zone ! Request time not localized.')
pretty_printer(log_obj = loggersrv.warning,
put_text = "{reverse}{yellow}{bold}Unknown time zone ! Request time not localized.{end}")
local_dt = requestDatetime
except ImportError:
loggersrv.warning('Module "tzlocal" not available ! Request time not localized.')
pretty_printer(log_obj = loggersrv.warning,
put_text = "{reverse}{yellow}{bold}Module 'tzlocal' not available ! Request time not localized.{end}")
local_dt = requestDatetime
# Activation threshold.
@ -144,15 +144,19 @@ class kmsBase:
if 0 < self.srv_config["CurrentClientCount"] < MinClients:
# fixed to 6 (product server) or 26 (product desktop)
currentClientCount = MinClients + 1
loggersrv.warning("Not enough clients ! Fixed with %s, but activated client could be detected as not genuine !" %currentClientCount)
pretty_printer(log_obj = loggersrv.warning,
put_text = "{reverse}{yellow}{bold}Not enough clients ! Fixed with %s, but activated client \
could be detected as not genuine !{end}" %currentClientCount)
elif MinClients <= self.srv_config["CurrentClientCount"] < RequiredClients:
currentClientCount = self.srv_config["CurrentClientCount"]
loggersrv.warning("With count = %s, activated client could be detected as not genuine !" %currentClientCount)
pretty_printer(log_obj = loggersrv.warning,
put_text = "{reverse}{yellow}{bold}With count = %s, activated client could be detected as not genuine !{end}" %currentClientCount)
elif self.srv_config["CurrentClientCount"] >= RequiredClients:
# fixed to 10 (product server) or 50 (product desktop)
currentClientCount = RequiredClients
if self.srv_config["CurrentClientCount"] > RequiredClients:
loggersrv.warning("Too many clients ! Fixed with %s" %currentClientCount)
pretty_printer(log_obj = loggersrv.warning,
put_text = "{reverse}{yellow}{bold}Too many clients ! Fixed with %s{end}" %currentClientCount)
else:
# fixed to 10 (product server) or 50 (product desktop)
currentClientCount = RequiredClients
@ -173,14 +177,16 @@ class kmsBase:
break
except:
skuName = skuId
loggersrv.warning("Can't find a name for this product !!")
pretty_printer(log_obj = loggersrv.warning,
put_text = "{reverse}{yellow}{bold}Can't find a name for this product !{end}")
try:
if uuid.UUID(appitem['Id']) == applicationId:
appName = appitem['DisplayName']
except:
appName = applicationId
loggersrv.warning("Can't find a name for this application group !!")
pretty_printer(log_obj = loggersrv.warning,
put_text = "{reverse}{yellow}{bold}Can't find a name for this application group !{end}")
infoDict = {
"machineName" : kmsRequest.getMachineName(),

View File

@ -1,7 +1,6 @@
#!/usr/bin/env python3
import re
import argparse
import binascii
import datetime
import random
@ -11,7 +10,7 @@ import sys
import uuid
import logging
import os
import errno
import threading
import pykms_RpcBind, pykms_RpcRequest
from pykms_Filetimes import dt_to_filetime
@ -23,12 +22,26 @@ from pykms_RequestV6 import kmsRequestV6
from pykms_RpcBase import rpcBase
from pykms_DB2Dict import kmsDB2Dict
from pykms_Misc import logger_create, check_logfile
from pykms_Format import justify, byterize, enco, deco, ShellMessage
from pykms_Misc import KmsParser, KmsException, KmsHelper
from pykms_Format import justify, byterize, enco, deco, ShellMessage, pretty_printer
clt_description = 'KMS Client Emulator written in Python'
clt_version = 'py-kms_2019-05-15'
clt_version = "py-kms_2020-02-02"
__license__ = "The Unlicense"
__author__ = u"Matteo an <SystemRage@protonmail.com>"
__url__ = "https://github.com/SystemRage/py-kms"
clt_description = "py-kms: KMS Client Emulator written in Python"
clt_config = {}
#---------------------------------------------------------------------------------------------------------------------------------------------------------
class client_thread(threading.Thread):
def __init__(self, name):
threading.Thread.__init__(self)
self.name = name
self.with_gui = False
def run(self):
clt_main(with_gui = self.with_gui)
#---------------------------------------------------------------------------------------------------------------------------------------------------------
loggerclt = logging.getLogger('logclt')
@ -48,33 +61,48 @@ will be generated.', 'def' : None, 'des' : "machineName"},
'choi' : ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "MINI"]},
'lfile' : {'help' : 'Use this option to set an output log file. The default is \"pykms_logclient.log\". Type \"STDOUT\" to view \
log info on stdout. Type \"FILESTDOUT\" to combine previous actions.',
'def' : os.path.dirname(os.path.abspath( __file__ )) + "/pykms_logclient.log", 'des' : "logfile"},
'def' : os.path.join(os.path.dirname(os.path.abspath(__file__)), 'pykms_logclient.log'), 'des' : "logfile"},
'lsize' : {'help' : 'Use this flag to set a maximum size (in MB) to the output log file. Desactivated by default.', 'def' : 0, 'des': "logsize"},
}
def client_options():
parser = argparse.ArgumentParser(description = clt_description, epilog = 'version: ' + clt_version)
parser.add_argument("ip", nargs = "?", action = "store", default = clt_options['ip']['def'], help = clt_options['ip']['help'], type = str)
parser.add_argument("port", nargs = "?", action = "store", default = clt_options['port']['def'], help = clt_options['port']['help'], type = int)
parser.add_argument("-m", "--mode", dest = clt_options['mode']['des'], default = clt_options['mode']['def'], choices = clt_options['mode']['choi'],
help = clt_options['mode']['help'], type = str)
parser.add_argument("-c", "--cmid", dest = clt_options['cmid']['des'], default = clt_options['cmid']['def'], help = clt_options['cmid']['help'], type = str)
parser.add_argument("-n", "--name", dest = clt_options['name']['des'] , default = clt_options['name']['def'], help = clt_options['name']['help'], type = str)
parser.add_argument("-V", "--loglevel", dest = clt_options['llevel']['des'], action = "store", choices = clt_options['llevel']['choi'],
default = clt_options['llevel']['def'], help = clt_options['llevel']['help'], type = str)
parser.add_argument("-F", "--logfile", nargs = "+", dest = clt_options['lfile']['des'], default = clt_options['lfile']['def'],
help = clt_options['lfile']['help'], type = str)
parser.add_argument("-S", "--logsize", dest = clt_options['lsize']['des'], action = "store", default = clt_options['lsize']['def'],
help = clt_options['lsize']['help'], type = float)
clt_config.update(vars(parser.parse_args()))
# Check logfile.
clt_config['logfile'] = check_logfile(clt_config['logfile'], clt_options['lfile']['def'], loggerclt)
try:
client_parser = KmsParser(description = clt_description, epilog = 'version: ' + clt_version, add_help = False, allow_abbrew = False)
except TypeError:
client_parser = KmsParser(description = clt_description, epilog = 'version: ' + clt_version, add_help = False)
client_parser.add_argument("ip", nargs = "?", action = "store", default = clt_options['ip']['def'],
help = clt_options['ip']['help'], type = str)
client_parser.add_argument("port", nargs = "?", action = "store", default = clt_options['port']['def'],
help = clt_options['port']['help'], type = int)
client_parser.add_argument("-m", "--mode", dest = clt_options['mode']['des'], default = clt_options['mode']['def'],
choices = clt_options['mode']['choi'], help = clt_options['mode']['help'], type = str)
client_parser.add_argument("-c", "--cmid", dest = clt_options['cmid']['des'], default = clt_options['cmid']['def'],
help = clt_options['cmid']['help'], type = str)
client_parser.add_argument("-n", "--name", dest = clt_options['name']['des'] , default = clt_options['name']['def'],
help = clt_options['name']['help'], type = str)
client_parser.add_argument("-V", "--loglevel", dest = clt_options['llevel']['des'], action = "store",
choices = clt_options['llevel']['choi'], default = clt_options['llevel']['def'],
help = clt_options['llevel']['help'], type = str)
client_parser.add_argument("-F", "--logfile", nargs = "+", action = "store", dest = clt_options['lfile']['des'],
default = clt_options['lfile']['def'], help = clt_options['lfile']['help'], type = str)
client_parser.add_argument("-S", "--logsize", dest = clt_options['lsize']['des'], action = "store",
default = clt_options['lsize']['def'], help = clt_options['lsize']['help'], type = float)
client_parser.add_argument("-h", "--help", action = "help", help = "show this help message and exit")
try:
if "-h" in sys.argv[1:]:
KmsHelper().printer(parsers = [client_parser])
clt_config.update(vars(client_parser.parse_args()))
except KmsException as e:
pretty_printer(put_text = "{reverse}{red}{bold}%s. Exiting...{end}" %str(e), to_exit = True)
def client_check():
# Check logfile.
clt_config['logfile'] = check_logfile(clt_config['logfile'], clt_options['lfile']['def'], where = "clt")
# Setup hidden or not messages.
ShellMessage.view = ( False if any(i in ['STDOUT', 'FILESTDOUT'] for i in clt_config['logfile']) else True )
# Create log.
logger_create(loggerclt, clt_config, mode = 'a')
@ -83,14 +111,13 @@ def client_check():
try:
uuid.UUID(clt_config['cmid'])
except ValueError:
loggerclt.error("Bad CMID. Exiting...")
sys.exit()
pretty_printer(log_obj = loggerclt.error, to_exit = True, where = "clt",
put_text = "{reverse}{red}{bold}Bad CMID. Exiting...{end}")
# Check machineName.
if clt_config['machineName'] is not None:
if len(clt_config['machineName']) < 2 or len(clt_config['machineName']) > 63:
loggerclt.error("machineName must be between 2 and 63 characters in length.")
sys.exit()
pretty_printer(log_obj = loggerclt.error, to_exit = True, where = "clt",
put_text = "{reverse}{red}{bold}machineName must be between 2 and 63 characters in length. Exiting...{end}")
clt_config['call_id'] = 1
@ -125,32 +152,47 @@ def client_create():
loggerclt.info("Connection successful !")
binder = pykms_RpcBind.handler(None, clt_config)
RPC_Bind = enco(str(binder.generateRequest()), 'latin-1')
loggerclt.info("Sending RPC bind request...")
ShellMessage.Process([-1, 1]).run()
s.send(RPC_Bind)
try:
ShellMessage.Process([-4, 7]).run()
bindResponse = s.recv(1024)
loggerclt.info("Sending RPC bind request...")
pretty_printer(num_text = [-1, 1], where = "clt")
s.send(RPC_Bind)
except socket.error as e:
if e.errno == errno.ECONNRESET:
loggerclt.error("Connection reset by peer. Exiting...")
sys.exit()
else:
raise
pretty_printer(log_obj = loggerclt.error, to_exit = True, where = "clt",
put_text = "{reverse}{red}{bold}While sending: %s{end}" %str(e))
try:
bindResponse = s.recv(1024)
if bindResponse == '' or not bindResponse:
loggerclt.error("No data received ! Exiting...")
sys.exit()
pretty_printer(log_obj = loggerclt.warning, to_exit = True, where = "clt",
put_text = "{reverse}{yellow}{bold}No data received.{end}")
pretty_printer(num_text = [-4, 7], where = "clt")
except socket.error as e:
pretty_printer(log_obj = loggerclt.error, to_exit = True, where = "clt",
put_text = "{reverse}{red}{bold}While receiving: %s{end}" %str(e))
packetType = MSRPCHeader(bindResponse)['type']
if packetType == rpcBase.packetType['bindAck']:
loggerclt.info("RPC bind acknowledged.")
ShellMessage.Process(8).run()
pretty_printer(num_text = 8, where = "clt")
kmsRequest = createKmsRequest()
requester = pykms_RpcRequest.handler(kmsRequest, clt_config)
s.send(enco(str(requester.generateRequest()), 'latin-1'))
ShellMessage.Process([-1, 12]).run()
try:
loggerclt.info("Sending RPC activation request...")
RPC_Actv = enco(str(requester.generateRequest()), 'latin-1')
pretty_printer(num_text = [-1, 12], where = "clt")
s.send(RPC_Actv)
except socket.error as e:
pretty_printer(log_obj = loggerclt.error, to_exit = True, where = "clt",
put_text = "{reverse}{red}{bold}While sending: %s{end}" %str(e))
try:
response = s.recv(1024)
pretty_printer(num_text = [-4, 20], where = "clt")
except socket.error as e:
pretty_printer(log_obj = loggerclt.error, to_exit = True, where = "clt",
put_text = "{reverse}{red}{bold}While receiving: %s{end}" %str(e))
loggerclt.debug("Response: \n%s\n" % justify(deco(binascii.b2a_hex(response), 'latin-1')))
ShellMessage.Process([-4, 20]).run()
parsed = MSRPCRespHeader(response)
kmsData = readKmsResponse(parsed['pduData'], kmsRequest, clt_config)
kmsResp = kmsData['response']
@ -170,15 +212,14 @@ def client_create():
'status' : "Activated",
'product' : clt_config["mode"]})
ShellMessage.Process(21).run()
pretty_printer(num_text = 21, where = "clt")
elif packetType == rpcBase.packetType['bindNak']:
loggerclt.info(justify(MSRPCBindNak(bindResponse).dump(print_to_stdout = False)))
sys.exit()
sys.exit(0)
else:
loggerclt.critical("Something went wrong.")
sys.exit()
pretty_printer(log_obj = loggerclt.warning, to_exit = True, where = "clt",
put_text = "{reverse}{magenta}{bold}Something went wrong.{end}")
def clt_main(with_gui = False):
if not with_gui:
@ -211,7 +252,7 @@ def createKmsRequestBase():
requestDict['mnPad'] = '\0'.encode('utf-16le') * (63 - len(requestDict['machineName'].decode('utf-16le')))
# Debug Stuff
ShellMessage.Process(9).run()
pretty_printer(num_text = 9, where = "clt")
requestDict = byterize(requestDict)
loggerclt.debug("Request Base Dictionary: \n%s\n" % justify(requestDict.dump(print_to_stdout = False)))

View File

@ -3,7 +3,8 @@
from __future__ import print_function, unicode_literals
import re
import sys
import threading
import os
from collections import OrderedDict
try:
# Python 2.x imports
@ -20,20 +21,17 @@ pyver = sys.version_info[:2]
def enco(strg, typ = 'latin-1'):
if pyver >= (3, 0):
if isinstance(strg, str):
strgenc = strg.encode(typ)
return strgenc
return strg.encode(typ)
else:
return strg
def deco(strg, typ = 'latin-1'):
if pyver >= (3, 0):
if isinstance(strg, bytes):
strgdec = strg.decode(typ)
return strgdec
return strg.decode(typ)
else:
return strg
def byterize(obj):
def do_encode(dictio, key):
@ -94,67 +92,96 @@ ExtraMap = {'end' : '\x1b[0m',
}
ColorExtraMap = dict(ColorMap, **ExtraMap)
ColorMapReversed = dict(zip(ColorMap.values(), ColorMap.keys()))
ExtraMapReversed = dict(zip(ExtraMap.values(), ExtraMap.keys()))
MsgMap = {0 : {'text' : "{yellow}\n\t\t\tClient generating RPC Bind Request...{end}", 'where' : "clt"},
1 : {'text' : "{white}<==============={end}{yellow}\tClient sending RPC Bind Request...{end}", 'where' : "clt"},
2 : {'text' : "{yellow}Server received RPC Bind Request !!!\t\t\t\t{end}{white}<==============={end}", 'where' : "srv"},
3 : {'text' : "{yellow}Server parsing RPC Bind Request...{end}", 'where' : "srv"},
4 : {'text' : "{yellow}Server generating RPC Bind Response...{end}", 'where' : "srv"},
5 : {'text' : "{yellow}Server sending RPC Bind Response...\t\t\t\t{end}{white}===============>{end}", 'where' : "srv"},
6 : {'text' : "{green}{bold}RPC Bind acknowledged !!!\n\n{end}", 'where' : "srv"},
7 : {'text' : "{white}===============>{end}{yellow}\tClient received RPC Bind Response !!!{end}", 'where' : "clt"},
8 : {'text' : "{green}{bold}\t\t\tRPC Bind acknowledged !!!\n{end}", 'where' : "clt"},
9 : {'text' : "{blue}\t\t\tClient generating Activation Request dictionary...{end}", 'where' : "clt"},
10 : {'text' : "{blue}\t\t\tClient generating Activation Request data...{end}", 'where' : "clt"},
11 : {'text' : "{blue}\t\t\tClient generating RPC Activation Request...{end}", 'where' : "clt"},
12 : {'text' : "{white}<==============={end}{blue}\tClient sending RPC Activation Request...\n\n{end}", 'where' : "clt"},
13 : {'text' : "{blue}Server received RPC Activation Request !!!\t\t\t{end}{white}<==============={end}", 'where' : "srv"},
14 : {'text' : "{blue}Server parsing RPC Activation Request...{end}", 'where' : "srv"},
15 : {'text' : "{blue}Server processing KMS Activation Request...{end}", 'where' : "srv"},
16 : {'text' : "{blue}Server processing KMS Activation Response...{end}", 'where' : "srv"},
17 : {'text' : "{blue}Server generating RPC Activation Response...{end}", 'where' : "srv"},
18 : {'text' : "{blue}Server sending RPC Activation Response...\t\t\t{end}{white}===============>{end}", 'where' : "srv"},
19 : {'text' : "{green}{bold}Server responded, now in Stand by...\n{end}", 'where' : "srv"},
20 : {'text' : "{white}===============>{end}{blue}\tClient received Response !!!{end}", 'where' : "clt"},
21 : {'text' : "{green}{bold}\t\t\tActivation Done !!!{end}", 'where' : "clt"},
-1 : {'text' : "{white}Server receiving{end}", 'where' : "clt"},
-2 : {'text' : "{white}\n\n\t\t\t\t\t\t\t\tClient sending{end}", 'where' : "srv"},
-3 : {'text' : "{white}\t\t\t\t\t\t\t\tClient receiving{end}", 'where' : "srv"},
-4 : {'text' : "{white}\n\nServer sending{end}", 'where' : "clt"},
40 : {'text' : "{red}{bold}Server connection timed out. Exiting...{end}", 'where' : "srv"},
41 : {'text' : "{red}{bold}HWID '{0}' is invalid. Digit {1} non hexadecimal. Exiting...{end}", 'where' : "srv"},
42 : {'text' : "{red}{bold}HWID '{0}' is invalid. Hex string is odd length. Exiting...{end}", 'where' : "srv"},
43 : {'text' : "{red}{bold}HWID '{0}' is invalid. Hex string is too short. Exiting...{end}", 'where' : "srv"},
44 : {'text' : "{red}{bold}HWID '{0}' is invalid. Hex string is too long. Exiting...{end}", 'where' : "srv"},
45 : {'text' : "{red}{bold}Port number '{0}' is invalid. Enter between 1 - 65535. Exiting...{end}", 'where' : "srv"},
46 : {'text' : "{red}{bold}{0}. Exiting...{end}", 'where' : "srv"},
MsgMap = {0 : {'text' : "{yellow}\n\t\t\tClient generating RPC Bind Request...{end}", 'align' : ()},
1 : {'text' : "{white}<==============={end}{yellow}\tClient sending RPC Bind Request...{end}", 'align' : ()},
2 : {'text' : "{yellow}Server received RPC Bind Request !!!\t\t\t\t{end}{white}<==============={end}", 'align' : ()},
3 : {'text' : "{yellow}Server parsing RPC Bind Request...{end}", 'align' : ()},
4 : {'text' : "{yellow}Server generating RPC Bind Response...{end}", 'align' : ()},
5 : {'text' : "{yellow}Server sending RPC Bind Response...\t\t\t\t{end}{white}===============>{end}", 'align' : ()},
6 : {'text' : "{green}{bold}\nRPC Bind acknowledged !!!{end}", 'align' : ()},
7 : {'text' : "{white}===============>{end}{yellow}\tClient received RPC Bind Response !!!{end}", 'align' : ()},
8 : {'text' : "{green}{bold}\t\t\tRPC Bind acknowledged !!!{end}", 'align' : ()},
9 : {'text' : "{blue}\t\t\tClient generating Activation Request dictionary...{end}", 'align' : ()},
10 : {'text' : "{blue}\t\t\tClient generating Activation Request data...{end}", 'align' : ()},
11 : {'text' : "{blue}\t\t\tClient generating RPC Activation Request...{end}", 'align' : ()},
12 : {'text' : "{white}<==============={end}{blue}\tClient sending RPC Activation Request...{end}", 'align' : ()},
13 : {'text' : "{blue}Server received RPC Activation Request !!!\t\t\t{end}{white}<==============={end}", 'align' : ()},
14 : {'text' : "{blue}Server parsing RPC Activation Request...{end}", 'align' : ()},
15 : {'text' : "{blue}Server processing KMS Activation Request...{end}", 'align' : ()},
16 : {'text' : "{blue}Server processing KMS Activation Response...{end}", 'align' : ()},
17 : {'text' : "{blue}Server generating RPC Activation Response...{end}", 'align' : ()},
18 : {'text' : "{blue}Server sending RPC Activation Response...\t\t\t{end}{white}===============>{end}", 'align' : ()},
19 : {'text' : "{green}{bold}\nServer responded, now in Stand by...\n{end}", 'align' : ()},
20 : {'text' : "{white}===============>{end}{blue}\tClient received Response !!!{end}", 'align' : ()},
21 : {'text' : "{green}{bold}\t\t\tActivation Done !!!{end}", 'align' : ()},
-1 : {'text' : "{white}Server receiving{end}", 'align' : ()},
-2 : {'text' : "{white}\t\t\t\t\t\t\t\tClient sending{end}", 'align' : ()},
-3 : {'text' : "{white}\t\t\t\t\t\t\t\tClient receiving{end}", 'align' : ()},
-4 : {'text' : "{white}Server sending{end}", 'align' : ()},
}
def pick_MsgMap(messagelist):
def unformat_message(symbolic_string_list):
""" `symbolic_string_list` : a list of strings with symbolic formattation, example:
symbolic_string_list = ["{yellow}\tPluto\n{end}",
"{reverse}{blue}======>{end}{red}\t\tPaperino{end}"]
>>> unformat_message(symbolic_string_list)
>>> [['\tPluto\n'], ['======>', '\t\tPaperino']]
"""
pattern = r"(?<!\{)\{([^}]+)\}(?!\})"
picktxt, pickarrw = [ [] for _ in range(2) ]
for messageitem in messagelist:
picklist = re.sub(pattern, '*', messageitem['text'])
picklist = list(filter(None, picklist.split('*')))
picktxt.append(picklist[0])
for item in symbolic_string_list:
try:
pickarrw.append(picklist[1])
except IndexError:
pass
return picktxt, pickarrw
# only for py-kms MsgMap.
picklist = re.sub(pattern, '*', item['text'])
except:
# generalization.
picklist = re.sub(pattern, '*', item)
picklist = list(filter(None, picklist.split('*')))
picktxt.append(picklist)
return picktxt
def unshell_MsgMap(arrows):
unMsgMap = {}
for key, values in MsgMap.items():
txt = pick_MsgMap([values])
def unshell_message(ansi_string, count):
""" `ansi_string` : a string with ansi formattation, example:
ansi_string = '\x1b[97mPippo\x1b[0m\n\x1b[94mPluto\t\t\x1b[0m\n\x1b[92m\x1b[1m\nPaperino\n\x1b[0m\n
`count` : int progressive increment for tag.
>>> unshell_message(ansi_string count = 0)
>>> ({'tag00': {'color': 'white', 'extra': [], 'text': 'Pippo'},
'tag01': {'color': 'blue', 'extra': [], 'text': 'Pluto\t\t'}
'tag02': {'color': 'green', 'extra': ['bold'], 'text': '\nPaperino\n'}
}, 3)
"""
ansi_find = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
ansi_list = re.findall(ansi_find, ansi_string)
ansi_indx_start = [ n for n in range(len(ansi_string)) for ansi in list(set(ansi_list)) if ansi_string.find(ansi, n) == n ]
ansi_indx_stop = [ n + len(value) for n, value in zip(ansi_indx_start, ansi_list)]
ansi_indx = sorted(list(set(ansi_indx_start + ansi_indx_stop)))
if txt[0][0] in arrows:
unMsgMap.update({txt[1][0] : values['where']})
msgcolored = {}
for k in range(len(ansi_indx) - 1):
ansi_value = ansi_string[ansi_indx[k] : ansi_indx[k + 1]]
if ansi_value not in ['\x1b[0m', '\n']:
tagname = "tag" + str(count).zfill(2)
if tagname not in msgcolored:
msgcolored[tagname] = {'color' : '', 'extra' : [], 'text' : ''}
if ansi_value in ColorMapReversed.keys():
msgcolored[tagname]['color'] = ColorMapReversed[ansi_value]
elif ansi_value in ExtraMapReversed.keys():
msgcolored[tagname]['extra'].append(ExtraMapReversed[ansi_value])
else:
unMsgMap.update({txt[0][0] : values['where']})
return unMsgMap
msgcolored[tagname]['text'] = ansi_value
else:
if ansi_value != '\n':
count += 1
# Ordering.
msgcolored = OrderedDict(sorted(msgcolored.items()))
return msgcolored, count
#-------------------------------------------------------------------------------------------------------------------------------------------------------
# https://stackoverflow.com/questions/230751/how-to-flush-output-of-print-function
@ -168,9 +195,13 @@ if pyver < (3, 3):
file = kwargs.get('file', sys.stdout)
file.flush() if file is not None else sys.stdout.flush()
# https://ryanjoneil.github.io/posts/2014-02-14-capturing-stdout-in-a-python-child-process.html
# based on: https://ryanjoneil.github.io/posts/2014-02-14-capturing-stdout-in-a-python-child-process.html,
# but not using threading/multiprocessing so:
# 1) message visualization order preserved.
# 2) newlines_count function output not wrong.
class ShellMessage(object):
view = True
count, remain, numlist = (0, 0, [])
class Collect(StringIO):
# Capture string sent to stdout.
@ -178,65 +209,121 @@ class ShellMessage(object):
StringIO.write(self, s)
class Process(object):
def __init__(self, nshell, get_text = False, put_text = None):
def __init__(self, nshell, get_text = False, put_text = None, where = 'srv'):
self.nshell = nshell
self.print_queue = Queue.Queue()
self.get_text = get_text
self.put_text = put_text
self.where = where
self.plaintext = []
self.path = os.path.dirname(os.path.abspath( __file__ )) + '/pykms_newlines.txt'
self.print_queue = Queue.Queue()
if not isinstance(nshell, list):
self.nshell = [nshell]
if not isinstance(put_text, list):
self.put_text = [put_text]
def formatter(self, num):
if self.put_text is None:
self.msg = MsgMap[num]['text'].format(**ColorExtraMap)
def formatter(self, msgtofrmt):
if self.newlines:
text = unformat_message([msgtofrmt])[0][0]
msgtofrmt = msgtofrmt['text'].replace(text, self.newlines * '\n' + text)
self.newlines = 0
else:
self.msg = MsgMap[num]['text'].format(*self.put_text, **ColorExtraMap)
try:
# comes from MsgMap.
msgtofrmt = msgtofrmt['text']
except:
# comes from `put_text` option.
pass
self.msgfrmt = msgtofrmt.format(**ColorExtraMap)
if self.get_text:
self.plaintext.append(unshell_message(self.msg, m = 0)[0]["tag00"]['text'])
self.plaintext.append(unshell_message(self.msgfrmt, count = 0)[0]["tag00"]['text'])
def newlines_file(self, mode, *args):
try:
with open(self.path, mode) as file:
if mode in ['w', 'a']:
file.write(args[0])
elif mode == 'r':
data = [int(i) for i in [line.rstrip('\n') for line in file.readlines()]]
self.newlines, ShellMessage.remain = data[0], sum(data[1:])
except:
with open(self.path, 'w') as file:
pass
def newlines_count(self, num):
ShellMessage.count += MsgMap[num]['text'].count('\n')
if num >= 0:
ShellMessage.numlist.append(num)
if self.continuecount:
# Note: is bypassed '\n' counted after message with arrow,
# so isn't: str(len(ShellMessage.numlist) + ShellMessage.count)
towrite = str(len(ShellMessage.numlist)) + '\n'
self.newlines_file('a', towrite)
ShellMessage.count, ShellMessage.numlist = (0, [])
else:
ShellMessage.count += (len(ShellMessage.numlist) - ShellMessage.remain) * 2
if num in [-1, -3]:
towrite = str(ShellMessage.count) + '\n'
self.newlines_file('w', towrite)
ShellMessage.count, ShellMessage.remain, ShellMessage.numlist = (0, 0, [])
self.continuecount = True
elif num in [-2 ,-4]:
self.newlines_file('r')
if num == 21:
ShellMessage.count, ShellMessage.remain, ShellMessage.numlist = (0, 0, [])
os.remove(self.path)
def run(self):
# view = False part.
if not ShellMessage.view:
if self.get_text:
self.newlines = 0
if self.put_text is not None:
for msg in self.put_text:
self.formatter(msg)
else:
for num in self.nshell:
self.formatter(num)
self.formatter(MsgMap[num])
return self.plaintext
else:
return
# Start thread process.
print_thread = threading.Thread(target = self.spawn(), args=(self.print_queue,))
print_thread.setDaemon(True)
print_thread.start()
# Do something with output.
toprint = self.read(0.1) # 0.1 s to let the shell output the result
# Do job.
self.produce()
toprint = self.consume(timeout = 0.1)
# Redirect output.
if sys.stdout.isatty():
print(toprint)
else:
try:
from pykms_GuiBase import gui_redirect # Import after variables creation.
gui_redirect(toprint)
# Import after variables creation.
from pykms_GuiBase import gui_redirect
gui_redirect(toprint, self.where)
except:
print(toprint)
# Get string/s printed.
if self.get_text:
return self.plaintext
def spawn(self):
def produce(self):
# Save everything that would otherwise go to stdout.
outstream = ShellMessage.Collect()
sys.stdout = outstream
try:
self.continuecount = False
self.newlines = 0
# Print something.
if self.put_text is not None:
for msg in self.put_text:
ShellMessage.count += msg.count('\n')
# Append a dummy element.
ShellMessage.numlist.append('put')
self.formatter(msg)
print(self.msgfrmt, end = '\n', flush = True)
else:
for num in self.nshell:
self.formatter(num)
print(self.msg, flush = True)
self.newlines_count(num)
self.formatter(MsgMap[num])
print(self.msgfrmt, end = '\n', flush = True)
except Exception as e:
print(e, end = '\n', flush = True)
finally:
# Restore stdout and send content.
sys.stdout = sys.__stdout__
@ -245,7 +332,7 @@ class ShellMessage(object):
except Queue.Full:
pass
def read(self, timeout = None):
def consume(self, timeout = None):
try:
toprint = self.print_queue.get(block = timeout is not None, timeout = timeout)
self.print_queue.task_done()
@ -253,34 +340,54 @@ class ShellMessage(object):
except Queue.Empty:
return None
def pretty_printer(**kwargs):
"""kwargs:
`log_obj` --> if logging object specified the text not ansi
formatted is logged.
`get_text` --> if True obtain text not ansi formatted,
after printing it with ansi formattation.
`put_text` --> a string or list of strings with ansi formattation.
if None refer to `num_text` for printing process.
`num_text` --> a number or list of numbers refering numbered message map.
if None `put_text` must be defined for printing process.
`to_exit ` --> if True system exit is called.
`where` --> specifies if message is server-side or client-side
(useful for GUI redirect).
"""
# Set defaults for not defined options.
options = {'log_obj' : None,
'get_text' : False,
'put_text' : None,
'num_text' : None,
'to_exit' : False,
'where' : 'srv'
}
options.update(kwargs)
# Check options.
if (options['num_text'] is None) and (options['put_text'] is None):
raise ValueError('One of `num_text` and `put_text` must be provided.')
elif (options['num_text'] is not None) and (options['put_text'] is not None):
raise ValueError('These parameters are mutually exclusive.')
def unshell_message(ansi_string, m):
ansi_find = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
ansi_list = re.findall(ansi_find, ansi_string)
ansi_indx_start = [ n for n in range(len(ansi_string)) for ansi in list(set(ansi_list)) if ansi_string.find(ansi, n) == n ]
ansi_indx_stop = [ n + len(value) for n, value in zip(ansi_indx_start, ansi_list)]
ansi_indx = sorted(list(set(ansi_indx_start + ansi_indx_stop)))
if (options['num_text'] is not None) and (not isinstance(options['num_text'], list)):
options['num_text'] = [options['num_text']]
if (options['put_text'] is not None) and (not isinstance(options['put_text'], list)):
options['put_text'] = [options['put_text']]
msgcolored = {}
ColorMapReversed = dict(zip(ColorMap.values(), ColorMap.keys()))
ExtraMapReversed = dict(zip(ExtraMap.values(), ExtraMap.keys()))
# Overwrite `get_text` (used as hidden).
if options['put_text']:
options['get_text'] = True
elif options['num_text']:
options['get_text'] = False
for k in range(len(ansi_indx) - 1):
ansi_value = ansi_string[ansi_indx[k] : ansi_indx[k + 1]]
if ansi_value != '\x1b[0m':
tagname = "tag" + str(m).zfill(2)
if tagname not in msgcolored:
msgcolored[tagname] = {'color' : '', 'extra' : [], 'text' : ''}
# Process messages.
plain_messages = ShellMessage.Process(options['num_text'],
get_text = options['get_text'],
put_text = options['put_text'],
where = options['where']).run()
if ansi_value in ColorMapReversed.keys():
msgcolored[tagname]['color'] = ColorMapReversed[ansi_value]
elif ansi_value in ExtraMapReversed.keys():
msgcolored[tagname]['extra'].append(ExtraMapReversed[ansi_value])
else:
msgcolored[tagname]['text'] = ansi_value
else:
m += 1
# Ordering.
msgcolored = dict(sorted(msgcolored.items()))
return msgcolored, m
if options['log_obj']:
for plain_message in plain_messages:
options['log_obj'](plain_message)
if options['to_exit']:
sys.exit(1)

View File

@ -3,6 +3,7 @@
import os
import sys
import threading
from time import sleep
try:
# Python 2.x imports
@ -19,12 +20,15 @@ except ImportError:
from tkinter import filedialog
import tkinter.font as tkFont
from pykms_Server import srv_options, srv_version, srv_config, serverqueue, serverthread
from pykms_GuiMisc import ToolTip, TextDoubleScroll, TextRedirect, custom_background, make_clear
from pykms_Client import clt_options, clt_version, clt_config, clt_main
from pykms_Server import srv_options, srv_version, srv_config, server_terminate, serverqueue, serverthread
from pykms_GuiMisc import ToolTip, TextDoubleScroll, TextRedirect, custom_background
from pykms_Client import clt_options, clt_version, clt_config, client_thread
gui_description = 'py-kms GUI'
gui_version = 'v1.0'
gui_version = "py-kms_gui_v2.0"
__license__ = "The Unlicense"
__author__ = u"Matteo an <SystemRage@protonmail.com>"
__url__ = "https://github.com/SystemRage/py-kms"
gui_description = "A GUI for py-kms."
##---------------------------------------------------------------------------------------------------------------------------------------------------------
def get_ip_address():
@ -40,26 +44,14 @@ def get_ip_address():
import socket
ip = socket.gethostbyname(socket.gethostname())
else:
ip = ''
print('Error: Couldn\'t get local ip')
ip = 'Unknown'
return ip
def switch_dir(path):
if os.path.isdir(path):
os.chdir(path)
return True
if path == '':
os.chdir(os.getcwd())
return True
else:
return
def gui_redirect(str_to_print):
global txsrv, txclt, txcol, rclt
def gui_redirect(str_to_print, where):
global txsrv, txclt, txcol
try:
TextRedirect.StdoutRedirect(txsrv, txclt, txcol, rclt, str_to_print)
TextRedirect.StdoutRedirect(txsrv, txclt, txcol, str_to_print, where)
except:
print(str_to_print)
@ -76,12 +68,14 @@ class KmsGui(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.wraplength = 200
serverthread.with_gui = True
self.validation_int = self.register(self.validate_int)
## Define fonts and colors.
self.btnwinfont = tkFont.Font(family = 'Times', size = 12, weight = 'bold')
self.othfont = tkFont.Font(family = 'Times', size = 9, weight = 'bold')
self.optfont = tkFont.Font(family = 'Helvetica', size = 11, weight = 'bold')
self.msgfont = tkFont.Font(family = 'Helvetica', size = 7)
self.msgfont = tkFont.Font(family = 'Monospace', size = 6) # need a monospaced type (like courier, etc..).
self.customcolors = { 'black' : '#000000',
'white' : '#FFFFFF',
@ -102,11 +96,10 @@ class KmsGui(tk.Tk):
## Create client gui + other operations.
self.gui_complete()
## Create globals for printing process (redirect stdout).
global txsrv, txclt, txcol, rclt
global txsrv, txclt, txcol
txsrv = self.textboxsrv.get()
txclt = self.textboxclt.get()
txcol = self.customcolors
rclt = self.runbtnclt
## Redirect stderr.
sys.stderr = TextRedirect.StderrRedirect(txsrv, txclt, txcol)
@ -131,14 +124,14 @@ class KmsGui(tk.Tk):
## Create widgets (btnsrvwin) -----------------------------------------------------------------------------------------------------------
self.statesrv = tk.Label(self.btnsrvwin, text = 'Server\nState:\nStopped', font = self.othfont, foreground = self.customcolors['red'])
self.runbtnsrv = tk.Button(self.btnsrvwin, text = 'START\nSERVER', background = self.customcolors['green'],
foreground = self.customcolors['white'], relief = 'flat', font = self.btnwinfont, command = self.srv_clickedmain)
foreground = self.customcolors['white'], relief = 'flat', font = self.btnwinfont, command = self.srv_on_start)
self.shbtnclt = tk.Button(self.btnsrvwin, text = 'SHOW\nCLIENT', background = self.customcolors['magenta'],
foreground = self.customcolors['white'], relief = 'flat', font = self.btnwinfont, command = self.clt_showhide)
foreground = self.customcolors['white'], relief = 'flat', font = self.btnwinfont, command = self.clt_on_show)
self.clearbtnsrv = tk.Button(self.btnsrvwin, text = 'CLEAR', background = self.customcolors['orange'],
foreground = self.customcolors['white'], relief = 'flat', font = self.btnwinfont,
command = lambda: make_clear([txsrv, txclt]))
command = lambda: self.on_clear([txsrv, txclt]))
self.exitbtnsrv = tk.Button(self.btnsrvwin, text = 'EXIT', background = self.customcolors['black'],
foreground = self.customcolors['white'], relief = 'flat', font = self.btnwinfont, command = self.destroy)
foreground = self.customcolors['white'], relief = 'flat', font = self.btnwinfont, command = self.on_exit)
## Layout widgets (btnsrvwin)
self.statesrv.grid(row = 0, column = 0, padx = 2, pady = 2, sticky = 'ew')
@ -151,72 +144,86 @@ class KmsGui(tk.Tk):
# Version.
ver = tk.Label(self.optsrvwin, text = 'You are running server version: ' + srv_version, foreground = self.customcolors['red'],
font = self.othfont)
self.allopts_srv = []
# Ip Address.
ipaddlbl = tk.Label(self.optsrvwin, text = 'IP Address: ', font = self.optfont)
self.ipadd = tk.Entry(self.optsrvwin, width = 10, font = self.optfont)
self.ipadd.insert('end', srv_options['ip']['def'])
ToolTip(self.ipadd, text = srv_options['ip']['help'], wraplength = self.wraplength)
srvipaddlbl = tk.Label(self.optsrvwin, text = 'IP Address: ', font = self.optfont)
self.srvipadd = tk.Entry(self.optsrvwin, width = 10, font = self.optfont)
self.srvipadd.insert('end', srv_options['ip']['def'])
ToolTip(self.srvipadd, text = srv_options['ip']['help'], wraplength = self.wraplength)
myipadd = tk.Label(self.optsrvwin, text = 'Your IP address is: {}'.format(get_ip_address()), foreground = self.customcolors['red'],
font = self.othfont)
self.allopts_srv.append(self.srvipadd)
# Port.
portlbl = tk.Label(self.optsrvwin, text = 'Port: ', font = self.optfont)
self.port = tk.Entry(self.optsrvwin, width = 10, font = self.optfont)
self.port.insert('end', str(srv_options['port']['def']))
ToolTip(self.port, text = srv_options['port']['help'], wraplength = self.wraplength)
srvportlbl = tk.Label(self.optsrvwin, text = 'Port: ', font = self.optfont)
self.srvport = tk.Entry(self.optsrvwin, width = 10, font = self.optfont, validate = "key", validatecommand = (self.validation_int, "%S"))
self.srvport.insert('end', str(srv_options['port']['def']))
ToolTip(self.srvport, text = srv_options['port']['help'], wraplength = self.wraplength)
self.allopts_srv.append(self.srvport)
# EPID.
epidlbl = tk.Label(self.optsrvwin, text = 'EPID: ', font = self.optfont)
self.epid = tk.Entry(self.optsrvwin, width = 10, font = self.optfont)
self.epid.insert('end', str(srv_options['epid']['def']))
ToolTip(self.epid, text = srv_options['epid']['help'], wraplength = self.wraplength)
self.allopts_srv.append(self.epid)
# LCID.
lcidlbl = tk.Label(self.optsrvwin, text = 'LCID: ', font = self.optfont)
self.lcid = tk.Entry(self.optsrvwin, width = 10, font = self.optfont)
self.lcid = tk.Entry(self.optsrvwin, width = 10, font = self.optfont, validate = "key", validatecommand = (self.validation_int, "%S"))
self.lcid.insert('end', str(srv_options['lcid']['def']))
ToolTip(self.lcid, text = srv_options['lcid']['help'], wraplength = self.wraplength)
self.allopts_srv.append(self.lcid)
# HWID.
hwidlbl = tk.Label(self.optsrvwin, text = 'HWID: ', font = self.optfont)
self.hwid = tk.Entry(self.optsrvwin, width = 10, font = self.optfont)
self.hwid.insert('end', srv_options['hwid']['def'])
ToolTip(self.hwid, text = srv_options['hwid']['help'], wraplength = self.wraplength)
self.allopts_srv.append(self.hwid)
# Client Count
countlbl = tk.Label(self.optsrvwin, text = 'Client Count: ', font = self.optfont)
self.count = tk.Entry(self.optsrvwin, width = 10, font = self.optfont)
self.count.insert('end', str(srv_options['count']['def']))
ToolTip(self.count, text = srv_options['count']['help'], wraplength = self.wraplength)
self.allopts_srv.append(self.count)
# Activation Interval.
activlbl = tk.Label(self.optsrvwin, text = 'Activation Interval: ', font = self.optfont)
self.activ = tk.Entry(self.optsrvwin, width = 10, font = self.optfont)
self.activ = tk.Entry(self.optsrvwin, width = 10, font = self.optfont, validate = "key", validatecommand = (self.validation_int, "%S"))
self.activ.insert('end', str(srv_options['activation']['def']))
ToolTip(self.activ, text = srv_options['activation']['help'], wraplength = self.wraplength)
self.allopts_srv.append(self.activ)
# Renewal Interval.
renewlbl = tk.Label(self.optsrvwin, text = 'Activation Interval: ', font = self.optfont)
self.renew = tk.Entry(self.optsrvwin, width = 10, font = self.optfont)
self.renew = tk.Entry(self.optsrvwin, width = 10, font = self.optfont, validate = "key", validatecommand = (self.validation_int, "%S"))
self.renew.insert('end', str(srv_options['renewal']['def']))
ToolTip(self.renew, text = srv_options['renewal']['help'], wraplength = self.wraplength)
self.allopts_srv.append(self.renew)
# Logfile.
filelbl = tk.Label(self.optsrvwin, text = 'Logfile Path / Name: ', font = self.optfont)
self.file = tk.Entry(self.optsrvwin, width = 10, font = self.optfont)
self.file.insert('end', srv_options['lfile']['def'])
ToolTip(self.file, text = srv_options['lfile']['help'], wraplength = self.wraplength)
filebtnwin = tk.Button(self.optsrvwin, text = 'Browse', command = lambda: self.browse(self.file, srv_options))
srvfilelbl = tk.Label(self.optsrvwin, text = 'Logfile Path / Name: ', font = self.optfont)
self.srvfile = tk.Entry(self.optsrvwin, width = 10, font = self.optfont)
self.srvfile.insert('end', srv_options['lfile']['def'])
self.srvfile.xview_moveto(1)
ToolTip(self.srvfile, text = srv_options['lfile']['help'], wraplength = self.wraplength)
self.allopts_srv.append(self.srvfile)
filebtnwin = tk.Button(self.optsrvwin, text = 'Browse', command = lambda: self.browse(self.srvfile, srv_options))
self.allopts_srv.append(filebtnwin)
# Loglevel.
levellbl = tk.Label(self.optsrvwin, text = 'Loglevel: ', font = self.optfont)
self.level = ttk.Combobox(self.optsrvwin, values = tuple(srv_options['llevel']['choi']), width = 10)
self.level.set(srv_options['llevel']['def'])
ToolTip(self.level, text = srv_options['llevel']['help'], wraplength = self.wraplength)
srvlevellbl = tk.Label(self.optsrvwin, text = 'Loglevel: ', font = self.optfont)
self.srvlevel = ttk.Combobox(self.optsrvwin, values = tuple(srv_options['llevel']['choi']), width = 10)
self.srvlevel.set(srv_options['llevel']['def'])
ToolTip(self.srvlevel, text = srv_options['llevel']['help'], wraplength = self.wraplength)
self.allopts_srv.append(self.srvlevel)
# Sqlite database.
self.chkval = tk.BooleanVar()
self.chkval.set(srv_options['sql']['def'])
chksql = tk.Checkbutton(self.optsrvwin, text = 'Create Sqlite\nDatabase', font = self.optfont, var = self.chkval)
ToolTip(chksql, text = srv_options['sql']['help'], wraplength = self.wraplength)
self.allopts_srv.append(chksql)
## Layout widgets (optsrvwin)
ver.grid(row = 0, column = 0, columnspan = 3, padx = 5, pady = 5, sticky = 'ew')
ipaddlbl.grid(row = 1, column = 0, padx = 5, pady = 5, sticky = 'e')
self.ipadd.grid(row = 1, column = 1, padx = 5, pady = 5, sticky = 'ew')
srvipaddlbl.grid(row = 1, column = 0, padx = 5, pady = 5, sticky = 'e')
self.srvipadd.grid(row = 1, column = 1, padx = 5, pady = 5, sticky = 'ew')
myipadd.grid(row = 2, column = 1, columnspan = 2, padx = 5, pady = 5, sticky = 'ew')
portlbl.grid(row = 3, column = 0, padx = 5, pady = 5, sticky = 'e')
self.port.grid(row = 3, column = 1, padx = 5, pady = 5, sticky = 'ew')
srvportlbl.grid(row = 3, column = 0, padx = 5, pady = 5, sticky = 'e')
self.srvport.grid(row = 3, column = 1, padx = 5, pady = 5, sticky = 'ew')
epidlbl.grid(row = 4, column = 0, padx = 5, pady = 5, sticky = 'e')
self.epid.grid(row = 4, column = 1, padx = 5, pady = 5, sticky = 'ew')
lcidlbl.grid(row = 5, column = 0, padx = 5, pady = 5, sticky = 'e')
@ -229,15 +236,15 @@ class KmsGui(tk.Tk):
self.activ.grid(row = 8, column = 1, padx = 5, pady = 5, sticky = 'ew')
renewlbl.grid(row = 9, column = 0, padx = 5, pady = 5, sticky = 'e')
self.renew.grid(row = 9, column = 1, padx = 5, pady = 5, sticky = 'ew')
filelbl.grid(row = 10, column = 0, padx = 5, pady = 5, sticky = 'e')
self.file.grid(row = 10, column = 1, padx = 5, pady = 5, sticky = 'ew')
srvfilelbl.grid(row = 10, column = 0, padx = 5, pady = 5, sticky = 'e')
self.srvfile.grid(row = 10, column = 1, padx = 5, pady = 5, sticky = 'ew')
filebtnwin.grid(row = 10, column = 2, padx = 5, pady = 5, sticky = 'ew')
levellbl.grid(row = 11, column = 0, padx = 5, pady = 5, sticky = 'e')
self.level.grid(row = 11, column = 1, padx = 5, pady = 5, sticky = 'ew')
srvlevellbl.grid(row = 11, column = 0, padx = 5, pady = 5, sticky = 'e')
self.srvlevel.grid(row = 11, column = 1, padx = 5, pady = 5, sticky = 'ew')
chksql.grid(row = 12, column = 1, padx = 5, pady = 5, sticky = 'ew')
## Create widgets and layout (msgsrvwin) -----------------------------------------------------------------------------------------------
self.textboxsrv = TextDoubleScroll(self.msgsrvwin, background = self.customcolors['black'], wrap = 'word', state = 'disabled',
self.textboxsrv = TextDoubleScroll(self.msgsrvwin, background = self.customcolors['black'], wrap = 'none', state = 'disabled',
relief = 'ridge', font = self.msgfont)
self.textboxsrv.put()
@ -286,7 +293,7 @@ class KmsGui(tk.Tk):
# Create widgets (btncltwin) ------------------------------------------------------------------------------------------------------------
self.runbtnclt = tk.Button(self.btncltwin, text = 'START\nCLIENT', background = self.customcolors['blue'],
foreground = self.customcolors['white'], relief = 'flat', font = self.btnwinfont,
state = 'disabled', command = self.clt_clickedstart)
state = 'disabled', command = self.clt_on_start)
#self.othbutt = tk.Button(self.btncltwin, text = 'Botton\n2', background = self.customcolors['green'],
# foreground = self.customcolors['white'], relief = 'flat', font = self.btnwinfont)
@ -299,42 +306,52 @@ class KmsGui(tk.Tk):
# Version.
cltver = tk.Label(self.optcltwin, text = 'You are running client version: ' + clt_version, foreground = self.customcolors['red'],
font = self.othfont)
self.allopts_clt = []
# Ip Address.
cltipaddlbl = tk.Label(self.optcltwin, text = 'IP Address: ', font = self.optfont)
self.cltipadd = tk.Entry(self.optcltwin, width = 10, font = self.optfont)
self.cltipadd.insert('end', clt_options['ip']['def'])
ToolTip(self.cltipadd, text = clt_options['ip']['help'], wraplength = self.wraplength)
self.allopts_clt.append(self.cltipadd)
# Port.
cltportlbl = tk.Label(self.optcltwin, text = 'Port: ', font = self.optfont)
self.cltport = tk.Entry(self.optcltwin, width = 10, font = self.optfont)
self.cltport = tk.Entry(self.optcltwin, width = 10, font = self.optfont, validate = "key", validatecommand = (self.validation_int, "%S"))
self.cltport.insert('end', str(clt_options['port']['def']))
ToolTip(self.cltport, text = clt_options['port']['help'], wraplength = self.wraplength)
self.allopts_clt.append(self.cltport)
# Mode.
cltmodelbl = tk.Label(self.optcltwin, text = 'Mode: ', font = self.optfont)
self.cltmode = ttk.Combobox(self.optcltwin, values = tuple(clt_options['mode']['choi']), width = 10)
self.cltmode.set(clt_options['mode']['def'])
ToolTip(self.cltmode, text = clt_options['mode']['help'], wraplength = self.wraplength)
self.allopts_clt.append(self.cltmode)
# CMID.
cltcmidlbl = tk.Label(self.optcltwin, text = 'CMID: ', font = self.optfont)
self.cltcmid = tk.Entry(self.optcltwin, width = 10, font = self.optfont)
self.cltcmid.insert('end', str(clt_options['cmid']['def']))
ToolTip(self.cltcmid, text = clt_options['cmid']['help'], wraplength = self.wraplength)
self.allopts_clt.append(self.cltcmid)
# Machine Name.
cltnamelbl = tk.Label(self.optcltwin, text = 'Machine Name: ', font = self.optfont)
self.cltname = tk.Entry(self.optcltwin, width = 10, font = self.optfont)
self.cltname.insert('end', str(clt_options['name']['def']))
ToolTip(self.cltname, text = clt_options['name']['help'], wraplength = self.wraplength)
self.allopts_clt.append(self.cltname)
# Logfile.
cltfilelbl = tk.Label(self.optcltwin, text = 'Logfile Path / Name: ', font = self.optfont)
self.cltfile = tk.Entry(self.optcltwin, width = 10, font = self.optfont)
self.cltfile.insert('end', clt_options['lfile']['def'])
self.cltfile.xview_moveto(1)
ToolTip(self.cltfile, text = clt_options['lfile']['help'], wraplength = self.wraplength)
self.allopts_clt.append(self.cltfile)
cltfilebtnwin = tk.Button(self.optcltwin, text = 'Browse', command = lambda: self.browse(self.cltfile, clt_options))
self.allopts_clt.append(cltfilebtnwin)
# Loglevel.
cltlevellbl = tk.Label(self.optcltwin, text = 'Loglevel: ', font = self.optfont)
self.cltlevel = ttk.Combobox(self.optcltwin, values = tuple(clt_options['llevel']['choi']), width = 10)
self.cltlevel.set(clt_options['llevel']['def'])
ToolTip(self.cltlevel, text = clt_options['llevel']['help'], wraplength = self.wraplength)
self.allopts_clt.append(self.cltlevel)
# Layout widgets (optcltwin)
cltver.grid(row = 0, column = 0, columnspan = 3, padx = 5, pady = 5, sticky = 'ew')
@ -355,18 +372,34 @@ class KmsGui(tk.Tk):
self.cltlevel.grid(row = 7, column = 1, padx = 5, pady = 5, sticky = 'ew')
# Create widgets and layout (msgcltwin) ----------------------------------------------------------------------------------------------------------
self.textboxclt = TextDoubleScroll(self.msgcltwin, background = self.customcolors['black'], wrap = 'word', state = 'disabled',
self.textboxclt = TextDoubleScroll(self.msgcltwin, background = self.customcolors['black'], wrap = 'none', state = 'disabled',
relief = 'ridge', font = self.msgfont)
self.textboxclt.put()
def proper_none(self, value):
def prep_option(self, value):
value = None if value == 'None' else value
try:
return int(value)
except TypeError:
except (TypeError, ValueError):
# is NONE or is a STRING.
return value
def clt_showhide(self, force = False):
def prep_logfile(self, optionlog):
if optionlog.startswith('FILESTDOUT '):
split = optionlog.split('FILESTDOUT ')
split[0] = 'FILESTDOUT'
return split
elif optionlog.startswith('STDOUT '):
split = optionlog.split('STDOUT ')
split[0] = 'STDOUT'
return split
else:
return optionlog
def validate_int(self, value):
return value.isdigit()
def clt_on_show(self, force = False):
if self.optcltwin.winfo_ismapped() or force:
self.shbtnclt['text'] = 'SHOW\nCLIENT'
self.optcltwin.grid_remove()
@ -378,87 +411,127 @@ class KmsGui(tk.Tk):
self.msgcltwin.grid()
self.btncltwin.place(x = self.btncltwin_X, y = self.btncltwin_Y, bordermode = 'inside', anchor = 'nw')
def srv_clickedmain(self):
def srv_on_start(self):
if self.runbtnsrv['text'] == 'START\nSERVER':
if self.srv_clickedstart():
self.runbtnsrv.configure(text = 'STOP\nSERVER', background = self.customcolors['red'],
foreground = self.customcolors['white'])
self.runbtnclt.configure(state = 'normal')
elif self.runbtnsrv['text'] == 'STOP\nSERVER':
self.srv_clickedstop()
self.runbtnsrv.configure(text = 'START\nSERVER', background = self.customcolors['green'],
foreground = self.customcolors['white'])
self.runbtnclt.configure(state = 'disabled')
self.srv_actions_start()
# wait for switch.
while not serverthread.is_running_server:
pass
def srv_clickedstart(self):
ok = False
if switch_dir(os.path.dirname(self.file.get())):
if self.file.get().lower().endswith('.log'):
# Load dict.
srv_config[srv_options['ip']['des']] = self.ipadd.get()
srv_config[srv_options['port']['des']] = int(self.port.get())
srv_config[srv_options['epid']['des']] = self.proper_none(self.epid.get())
srv_config[srv_options['lcid']['des']] = int(self.lcid.get())
self.on_clear([txsrv, txclt])
self.srv_toggle_all(on_start = True)
# run thread for interrupting server when an error happens.
self.srv_eject_thread = threading.Thread(target = self.srv_eject, name = "Thread-SrvEjt")
self.srv_eject_thread.setDaemon(True)
self.srv_eject_thread.start()
elif self.runbtnsrv['text'] == 'STOP\nSERVER':
serverthread.terminate_eject()
def srv_eject(self):
while not serverthread.eject:
sleep(0.1)
self.srv_actions_stop()
def srv_actions_start(self):
srv_config[srv_options['ip']['des']] = self.srvipadd.get()
srv_config[srv_options['port']['des']] = self.prep_option(self.srvport.get())
srv_config[srv_options['epid']['des']] = self.prep_option(self.epid.get())
srv_config[srv_options['lcid']['des']] = self.prep_option(self.lcid.get())
srv_config[srv_options['hwid']['des']] = self.hwid.get()
srv_config[srv_options['count']['des']] = self.proper_none(self.count.get())
srv_config[srv_options['activation']['des']] = int(self.activ.get())
srv_config[srv_options['renewal']['des']] = int(self.renew.get())
srv_config[srv_options['lfile']['des']] = self.file.get()
srv_config[srv_options['llevel']['des']] = self.level.get()
srv_config[srv_options['count']['des']] = self.prep_option(self.count.get())
srv_config[srv_options['activation']['des']] = self.prep_option(self.activ.get())
srv_config[srv_options['renewal']['des']] = self.prep_option(self.renew.get())
srv_config[srv_options['lfile']['des']] = self.prep_logfile(self.srvfile.get())
srv_config[srv_options['llevel']['des']] = self.srvlevel.get()
srv_config[srv_options['sql']['des']] = self.chkval.get()
## TODO.
srv_config[srv_options['lsize']['des']] = 0
srv_config[srv_options['time']['des']] = 30
srv_config[srv_options['time']['des']] = None
serverqueue.put('start')
def srv_actions_stop(self):
if serverthread.is_running_server:
if serverthread.server is not None:
server_terminate(serverthread, exit_server = True)
# wait for switch.
while not serverthread.is_running:
while serverthread.is_running_server:
pass
self.srv_togglestate()
ok = True
else:
messagebox.showerror('Invalid extension', 'Not a .log file !')
serverthread.is_running_server = False
self.srv_toggle_all(on_start = False)
def srv_toggle_all(self, on_start = True):
self.srv_toggle_state()
if on_start:
self.runbtnsrv.configure(text = 'STOP\nSERVER', background = self.customcolors['red'],
foreground = self.customcolors['white'])
for widget in self.allopts_srv:
widget.configure(state = 'disabled')
self.runbtnclt.configure(state = 'normal')
else:
messagebox.showerror('Invalid path', 'Path you have provided not found !')
return ok
self.runbtnsrv.configure(text = 'START\nSERVER', background = self.customcolors['green'],
foreground = self.customcolors['white'])
for widget in self.allopts_srv:
widget.configure(state = 'normal')
self.runbtnclt.configure(state = 'disabled')
def srv_clickedstop(self):
if serverthread.is_running:
serverqueue.put('stop')
serverthread.server.shutdown()
# wait for switch.
while serverthread.is_running:
pass
self.srv_togglestate()
def srv_togglestate(self):
if serverthread.is_running:
def srv_toggle_state(self):
if serverthread.is_running_server:
txt, color = ('Server\nState:\nServing', self.customcolors['green'])
else:
txt, color = ('Server\nState:\nStopped', self.customcolors['red'])
self.statesrv.configure(text = txt, foreground = color)
def clt_clickedstart(self):
if switch_dir(os.path.dirname(self.cltfile.get())):
if self.cltfile.get().lower().endswith('.log'):
# Load dict.
def clt_on_start(self):
self.clt_actions_start()
# run thread for disabling interrupt server and client, when client running.
self.clt_eject_thread = threading.Thread(target = self.clt_eject, name = "Thread-CltEjt")
self.clt_eject_thread.setDaemon(True)
self.clt_eject_thread.start()
self.on_clear([txsrv, txclt])
for widget in self.allopts_clt + [self.runbtnsrv, self.runbtnclt]:
widget.configure(state = 'disabled')
def clt_actions_start(self):
clt_config[clt_options['ip']['des']] = self.cltipadd.get()
clt_config[clt_options['port']['des']] = int(self.cltport.get())
clt_config[clt_options['port']['des']] = self.prep_option(self.cltport.get())
clt_config[clt_options['mode']['des']] = self.cltmode.get()
clt_config[clt_options['cmid']['des']] = self.proper_none(self.cltcmid.get())
clt_config[clt_options['name']['des']] = self.proper_none(self.cltname.get())
clt_config[clt_options['lfile']['des']] = self.cltfile.get()
clt_config[clt_options['cmid']['des']] = self.prep_option(self.cltcmid.get())
clt_config[clt_options['name']['des']] = self.prep_option(self.cltname.get())
clt_config[clt_options['llevel']['des']] = self.cltlevel.get()
## TODO
clt_config[clt_options['lfile']['des']] = self.prep_logfile(self.cltfile.get())
## TODO.
clt_config[clt_options['lsize']['des']] = 0
# run client (in a thread).
self.clientthread = client_thread(name = "Thread-Clt")
self.clientthread.setDaemon(True)
self.clientthread.with_gui = True
self.clientthread.start()
clientthread = threading.Thread(target = clt_main, args=(True,))
clientthread.setDaemon(True)
clientthread.start()
def clt_eject(self):
while self.clientthread.is_alive():
sleep(0.1)
for widget in self.allopts_clt + [self.runbtnsrv, self.runbtnclt]:
widget.configure(state = 'normal')
def on_exit(self):
if serverthread.is_running_server:
if serverthread.server is not None:
server_terminate(serverthread, exit_server = True)
else:
messagebox.showerror('Invalid extension', 'Not a .log file !')
else:
messagebox.showerror('Invalid path', 'Path you have provided not found !')
serverthread.is_running_server = False
server_terminate(serverthread, exit_thread = True)
self.destroy()
def on_clear(self, widgetlist):
for widget in widgetlist:
widget.configure(state = 'normal')
widget.delete('1.0', 'end')
widget.configure(state = 'disabled')

View File

@ -16,7 +16,7 @@ except ImportError:
from tkinter import ttk
import tkinter.font as tkFont
from pykms_Format import unshell_message, MsgMap, pick_MsgMap, unshell_MsgMap
from pykms_Format import MsgMap, unshell_message, unformat_message
#---------------------------------------------------------------------------------------------------------------------------------------------------------
@ -123,107 +123,94 @@ class ToolTip(object):
# https://stackoverflow.com/questions/3029816/how-do-i-get-a-thread-safe-print-in-python-2-6
# https://stackoverflow.com/questions/20303291/issue-with-redirecting-stdout-to-tkinter-text-widget-with-threads
def make_clear(widgetlist):
for widget in widgetlist:
widget.configure(state = 'normal')
widget.delete('1.0', 'end')
widget.configure(state = 'disabled')
class TextRedirect(object):
class StdoutRedirect(object):
tag_num = 0
listwhere = []
arrows, clt_msg_nonewline = pick_MsgMap([MsgMap[1], MsgMap[7], MsgMap[12], MsgMap[20]])
srv_msg_nonewline, _ = pick_MsgMap([MsgMap[2], MsgMap[5], MsgMap[13], MsgMap[18]])
grpmsg = unformat_message([MsgMap[1], MsgMap[7], MsgMap[12], MsgMap[20]])
arrows = [ item[0] for item in grpmsg ]
clt_msg_nonewline = [ item[1] for item in grpmsg ]
arrows = list(set(arrows))
lenarrow = len(arrows[0])
unMsgMap = unshell_MsgMap(arrows)
srv_msg_nonewline = [ item[0] for item in unformat_message([MsgMap[2], MsgMap[5], MsgMap[13], MsgMap[18]]) ]
terminator = unformat_message([MsgMap[21]])[0][0]
msg_align = [ msg[0].replace('\t', '').replace('\n', '') for msg in unformat_message([MsgMap[-2], MsgMap[-4]])]
newlinecut = [-1, -2, -4, -5]
def __init__(self, srv_text_space, clt_text_space, customcolors, runclt, str_to_print):
def __init__(self, srv_text_space, clt_text_space, customcolors, str_to_print, where):
self.srv_text_space = srv_text_space
self.clt_text_space = clt_text_space
self.customcolors = customcolors
self.runclt = runclt
self.runclt.configure(state = 'disabled')
self.str_to_print = str_to_print
self.where = where
self.textbox_do()
def textbox_finish(self, message):
if all(x == "srv" for x in TextRedirect.StdoutRedirect.listwhere):
terminator = pick_MsgMap([MsgMap[19]])[0]
else:
terminator = pick_MsgMap([MsgMap[21]])[0]
if message in terminator:
if message == self.terminator:
TextRedirect.StdoutRedirect.tag_num = 0
self.runclt.configure(state = 'normal')
def textbox_clear(self):
if TextRedirect.StdoutRedirect.tag_num == 0:
# Clear "srv" and "clt" textboxs.
make_clear([self.srv_text_space, self.clt_text_space])
TextRedirect.StdoutRedirect.newlinecut = [-1, -2, -4, -5]
def textbox_write(self, tag, message, color, extras):
widget = self.textbox_choose(message)
TextRedirect.StdoutRedirect.listwhere.append(self.where)
self.maxchar = widget['width']
self.textbox_color(tag, widget, color, self.customcolors['black'], extras)
self.w_maxpix, self.h_maxpix = widget.winfo_width(), widget.winfo_height()
self.xfont = tkFont.Font(font = widget['font'])
widget.configure(state = 'normal')
widget.insert('end', self.textbox_format(message), tag)
widget.see('end')
self.textbox_color(tag, widget, color, self.customcolors['black'], extras)
widget.after(100, widget.see('end'))
widget.configure(state = 'disabled')
self.textbox_finish(message)
def textbox_choose(self, message):
if message not in self.arrows:
self.remind = message
self.where = self.unMsgMap[message]
if self.where == "srv":
self.srv_text_space.focus_set()
return self.srv_text_space
elif self.where == "clt":
return self.clt_text_space
else:
if self.remind in self.srv_msg_nonewline:
self.where = "srv"
return self.srv_text_space
else:
self.where = "clt"
self.clt_text_space.focus_set()
return self.clt_text_space
def textbox_color(self, tag, widget, forecolor = 'white', backcolor = 'black', extras = []):
xfont = tkFont.Font(font = widget['font'])
for extra in extras:
if extra == 'bold':
xfont.configure(weight = "bold")
self.xfont.configure(weight = "bold")
elif extra == 'italic':
xfont.configure(slant = "italic")
self.xfont.configure(slant = "italic")
elif extra == 'underlined':
xfont.text_font.configure(underline = True)
self.xfont.text_font.configure(underline = True)
elif extra == 'strike':
xfont.configure(overstrike = True)
self.xfont.configure(overstrike = True)
elif extra == 'reverse':
forecolor, backcolor = backcolor, forecolor
widget.tag_configure(tag, foreground = forecolor, background = backcolor, font = xfont)
widget.tag_configure(tag, foreground = forecolor, background = backcolor, font = self.xfont)
widget.tag_add(tag, "insert linestart", "insert lineend")
def textbox_newline(self, message):
if not message.endswith('\n'):
return message + '\n'
else:
return message
def textbox_format(self, message):
lenfixed = self.maxchar - len(message.replace('\t', ''))
# vertical align.
self.w_maxpix = self.w_maxpix - 5 # pixel reduction for distance from border.
w_fontpix, h_fontpix = (self.xfont.measure('0'), self.xfont.metrics('linespace'))
msg_unformat = message.replace('\t', '').replace('\n', '')
lenfixed_chars = int((self.w_maxpix / w_fontpix) - len(msg_unformat))
if self.where == "srv":
if message in self.srv_msg_nonewline:
lung = lenfixed - self.lenarrow + 4
else:
lung = lenfixed + self.lenarrow + 10
if not message.endswith('\n'):
message += '\n'
elif self.where == "clt":
if message in self.srv_msg_nonewline + self.clt_msg_nonewline:
lung = lenfixed_chars - self.lenarrow
if message in self.clt_msg_nonewline:
lung = lenfixed - self.lenarrow
if not message.endswith('\n'):
message += '\n'
message = self.textbox_newline(message)
else:
lung = lenfixed + 10
if not message.endswith('\n') and message not in self.arrows:
message += '\n'
lung = lenfixed_chars
if (self.where == "srv") or (self.where == "clt" and message not in self.arrows):
message = self.textbox_newline(message)
# horizontal align.
if msg_unformat in self.msg_align:
msg_strip = message.lstrip('\n')
message = '\n' * (len(message) - len(msg_strip) + TextRedirect.StdoutRedirect.newlinecut[0]) + msg_strip
TextRedirect.StdoutRedirect.newlinecut.pop(0)
count = Counter(message)
countab = (count['\t'] if count['\t'] != 0 else 1)
@ -231,7 +218,6 @@ class TextRedirect(object):
return message
def textbox_do(self):
self.textbox_clear()
msgs, TextRedirect.StdoutRedirect.tag_num = unshell_message(self.str_to_print, TextRedirect.StdoutRedirect.tag_num)
for tag in msgs:
self.textbox_write(tag, msgs[tag]['text'], self.customcolors[msgs[tag]['color']], msgs[tag]['extra'])
@ -242,9 +228,9 @@ class TextRedirect(object):
self.clt_text_space = clt_text_space
self.customcolors = customcolors
self.tag_err = 'STDERR'
self.xfont = tkFont.Font(font = self.srv_text_space['font'])
def write(self, string):
self.textbox_clear()
self.textbox_color(self.tag_err, self.srv_text_space, self.customcolors['red'], self.customcolors['black'])
self.srv_text_space.configure(state = 'normal')
self.srv_text_space.insert('end', string, self.tag_err)
@ -327,7 +313,7 @@ def custom_background(window):
widget.configure(background = window.customcolors['lavender'])
# Hide client.
window.clt_showhide(force = True)
window.clt_on_show(force = True)
# Show Gui.
window.deiconify()

View File

@ -1,10 +1,12 @@
#!/usr/bin/env python3
from __future__ import print_function
import sys
import logging
import os
import argparse
from logging.handlers import RotatingFileHandler
from pykms_Format import ColorExtraMap, ShellMessage
from pykms_Format import ColorExtraMap, pretty_printer
#-----------------------------------------------------------------------------------------------------------------------------------------------------------
@ -132,20 +134,25 @@ def logger_create(log_obj, config, mode = 'a'):
#----------------------------------------------------------------------------------------------------------------------------------------------------------
def check_logfile(optionlog, defaultlog, logger):
def check_logfile(optionlog, defaultlog, where):
if not isinstance(optionlog, list):
optionlog = [optionlog]
lenopt = len(optionlog)
msg_long = "argument logfile: too much arguments"
msg_dir = "{reverse}{red}{bold}argument logfile: invalid directory: '%s'. Exiting...{end}"
msg_long = "{reverse}{red}{bold}argument logfile: too much arguments. Exiting...{end}"
msg_log = "{reverse}{red}{bold}argument logfile: not a log file, invalid extension: '%s'. Exiting...{end}"
def checkdir(path):
msg_path = "argument logfile: No such file or directory: %s" %path
if not os.path.isdir(os.path.dirname(path)):
pretty_errors(46, logger, get_text = False, put_text = msg_path, log_text = False)
filename = os.path.basename(path)
pathname = os.path.dirname(path)
if not os.path.isdir(pathname):
pretty_printer(put_text = msg_dir %pathname, where = where, to_exit = True)
elif not filename.lower().endswith('.log'):
pretty_printer(put_text = msg_log %filename, where = where, to_exit = True)
if lenopt > 2:
pretty_errors(46, logger, get_text = False, put_text = msg_long, log_text = False)
pretty_printer(put_text = msg_long, where = where, to_exit = True)
if 'FILESTDOUT' in optionlog:
if lenopt == 1:
@ -156,36 +163,12 @@ def check_logfile(optionlog, defaultlog, logger):
checkdir(optionlog[1])
else:
if lenopt == 2:
pretty_errors(46, logger, get_text = False, put_text = msg_long, log_text = False)
pretty_printer(put_text = msg_long, where = where, to_exit = True)
elif lenopt == 1 and 'STDOUT' not in optionlog:
# check directory path.
checkdir(optionlog[0])
return optionlog
def pretty_errors(error_num, logger, **kwargs):
""" error_num --> an int or list of int.
kwargs:
get_text --> True (default) / False.
put_text --> string / list of strings/ None. (applied to each "error_num")
log_text --> True (default) / False.
to_exit --> True (default) / False.
"""
# Set defaults for not defined options.
options = {'get_text' : True,
'put_text' : None,
'log_text' : True,
'to_exit' : True,
}
options.update(kwargs)
# Process errors.
error_msgs = ShellMessage.Process(error_num, get_text = options['get_text'], put_text = options['put_text']).run()
if options['log_text']:
for err in error_msgs:
logger.error(err)
if options['to_exit']:
sys.exit(1)
#----------------------------------------------------------------------------------------------------------------------------------------------------------
# Valid language identifiers to be used in the EPID (see "kms.c" in vlmcsd)
@ -210,7 +193,7 @@ ValidLcid = [1025, 1026, 1027, 1028, 1029,
13313, 13321, 13322, 14337, 14346, 15361, 15370, 16385, 16394, 17418, 18442, 19466, 20490]
# http://stackoverflow.com/questions/3425294/how-to-detect-the-os-default-language-in-python
def check_lcid(lcid, logger):
def check_lcid(lcid, log_obj):
if not lcid or (lcid not in ValidLcid):
if hasattr(sys, 'implementation') and sys.implementation.name == 'cpython':
fixlcid = 1033
@ -225,12 +208,55 @@ def check_lcid(lcid, logger):
fixlcid = next(k for k, v in locale.windows_locale.items() if v == locale.getdefaultlocale()[0])
except StopIteration:
fixlcid = 1033
logger.warning("lcid %s auto-fixed with lcid %s" %(lcid, fixlcid))
pretty_printer(log_obj = log_obj,
put_text = "{reverse}{yellow}{bold}LCID %s auto-fixed with LCID %s{end}" %(lcid, fixlcid))
return fixlcid
return lcid
#----------------------------------------------------------------------------------------------------------------------------------------------------------
class KmsException(Exception):
pass
class KmsParser(argparse.ArgumentParser):
def error(self, message):
raise KmsException(message)
class KmsHelper(object):
def replace(self, parser, replace_epilog_with):
text = parser.format_help().splitlines()
help_list = []
for line in text:
if line == parser.description:
continue
if line == parser.epilog:
line = replace_epilog_with
help_list.append(line)
return help_list
def printer(self, parsers):
if len(parsers) == 3:
parser_base, parser_adj, parser_sub = parsers
replace_epilog_with = 80 * '*' + '\n'
elif len(parsers) == 1:
parser_base = parsers[0]
replace_epilog_with = ''
print('\n' + parser_base.description)
print(len(parser_base.description) * '-' + '\n')
for line in self.replace(parser_base, replace_epilog_with):
print(line)
try:
print(parser_adj.description + '\n')
for line in self.replace(parser_sub, replace_epilog_with):
print(line)
except:
pass
print('\n' + len(parser_base.epilog) * '-')
print(parser_base.epilog + '\n')
parser_base.exit()
#----------------------------------------------------------------------------------------------------------------------------------------------------------
# http://joshpoley.blogspot.com/2011/09/hresults-user-0x004.html (slerror.h)
ErrorCodes = {
'SL_E_SRV_INVALID_PUBLISH_LICENSE' : (0xC004B001, 'The activation server determined that the license is invalid.'),

View File

@ -7,7 +7,7 @@ import logging
from pykms_Base import kmsBase
from pykms_Structure import Structure
from pykms_Aes import AES
from pykms_Format import justify, byterize, enco, deco, ShellMessage
from pykms_Format import justify, byterize, enco, deco, pretty_printer
#---------------------------------------------------------------------------------------------------------------------------------------------------------
@ -105,7 +105,7 @@ class kmsRequestV4(kmsBase):
response['padding'] = bytes(bytearray(self.getPadding(bodyLength)))
## Debug stuff.
ShellMessage.Process(16).run()
pretty_printer(num_text = 16, where = "srv")
response = byterize(response)
loggersrv.debug("KMS V4 Response: \n%s\n" % justify(response.dump(print_to_stdout = False)))
loggersrv.debug("KMS V4 Response Bytes: \n%s\n" % justify(deco(binascii.b2a_hex(enco(str(response), 'latin-1')), 'utf-8')))
@ -124,7 +124,7 @@ class kmsRequestV4(kmsBase):
request['padding'] = bytes(bytearray(self.getPadding(bodyLength)))
## Debug stuff.
ShellMessage.Process(10).run()
pretty_printer(num_text = 10, where = "clt")
request = byterize(request)
loggersrv.debug("Request V4 Data: \n%s\n" % justify(request.dump(print_to_stdout = False)))
loggersrv.debug("Request V4: \n%s\n" % justify(deco(binascii.b2a_hex(enco(str(request), 'latin-1')), 'utf-8')))

View File

@ -8,7 +8,7 @@ import random
import pykms_Aes as aes
from pykms_Base import kmsBase
from pykms_Structure import Structure
from pykms_Format import justify, byterize, enco, deco, ShellMessage
from pykms_Format import justify, byterize, enco, deco, pretty_printer
#--------------------------------------------------------------------------------------------------------------------------------------------------------
@ -140,7 +140,7 @@ class kmsRequestV5(kmsBase):
response['encrypted'] = bytes(bytearray(encryptedResponse))
response['padding'] = bytes(bytearray(self.getPadding(bodyLength)))
ShellMessage.Process(16).run()
pretty_printer(num_text = 16, where = "srv")
response = byterize(response)
loggersrv.info("KMS V%d Response: \n%s\n" % (self.ver, justify(response.dump(print_to_stdout = False))))
loggersrv.info("KMS V%d Structure Bytes: \n%s\n" % (self.ver, justify(deco(binascii.b2a_hex(enco(str(response), 'latin-1')), 'utf-8'))))
@ -172,7 +172,7 @@ class kmsRequestV5(kmsBase):
request['versionMajor'] = requestBase['versionMajor']
request['message'] = message
ShellMessage.Process(10).run()
pretty_printer(num_text = 10, where = "clt")
request = byterize(request)
loggersrv.info("Request V%d Data: \n%s\n" % (self.ver, justify(request.dump(print_to_stdout = False))))
loggersrv.info("Request V%d: \n%s\n" % (self.ver, justify(deco(binascii.b2a_hex(enco(str(request), 'latin-1')), 'utf-8'))))

View File

@ -7,7 +7,7 @@ import uuid
import pykms_RpcBase
from pykms_Dcerpc import MSRPCHeader, MSRPCBindAck
from pykms_Structure import Structure
from pykms_Format import justify, byterize, enco, deco, ShellMessage
from pykms_Format import justify, byterize, enco, deco, pretty_printer
#--------------------------------------------------------------------------------------------------------------------------------------------------------
@ -77,7 +77,7 @@ class MSRPCBind(Structure):
class handler(pykms_RpcBase.rpcBase):
def parseRequest(self):
request = MSRPCHeader(self.data)
ShellMessage.Process(3).run()
pretty_printer(num_text = 3, where = "srv")
request = byterize(request)
loggersrv.debug("RPC Bind Request Bytes: \n%s\n" % justify(deco(binascii.b2a_hex(self.data), 'utf-8')))
loggersrv.debug("RPC Bind Request: \n%s\n%s\n" % (justify(request.dump(print_to_stdout = False)),
@ -121,7 +121,7 @@ class handler(pykms_RpcBase.rpcBase):
resp = preparedResponses[ts_uuid]
response['ctx_items'] += str(resp)
ShellMessage.Process(4).run()
pretty_printer(num_text = 4, where = "srv")
response = byterize(response)
loggersrv.debug("RPC Bind Response: \n%s\n" % justify(response.dump(print_to_stdout = False)))
loggersrv.debug("RPC Bind Response Bytes: \n%s\n" % justify(deco(binascii.b2a_hex(enco(str(response), 'latin-1')), 'utf-8')))
@ -162,7 +162,7 @@ class handler(pykms_RpcBase.rpcBase):
request['call_id'] = self.srv_config['call_id']
request['pduData'] = str(bind)
ShellMessage.Process(0).run()
pretty_printer(num_text = 0, where = "clt")
bind = byterize(bind)
request = byterize(request)
loggersrv.debug("RPC Bind Request: \n%s\n%s\n" % (justify(request.dump(print_to_stdout = False)),

View File

@ -6,7 +6,7 @@ import logging
import pykms_Base
import pykms_RpcBase
from pykms_Dcerpc import MSRPCRequestHeader, MSRPCRespHeader
from pykms_Format import justify, byterize, enco, deco, ShellMessage
from pykms_Format import justify, byterize, enco, deco, pretty_printer
#----------------------------------------------------------------------------------------------------------------------------------------------------------
@ -15,7 +15,7 @@ loggersrv = logging.getLogger('logsrv')
class handler(pykms_RpcBase.rpcBase):
def parseRequest(self):
request = MSRPCRequestHeader(self.data)
ShellMessage.Process(14).run()
pretty_printer(num_text = 14, where = "srv")
request = byterize(request)
loggersrv.debug("RPC Message Request Bytes: \n%s\n" % justify(binascii.b2a_hex(self.data).decode('utf-8')))
loggersrv.debug("RPC Message Request: \n%s\n" % justify(request.dump(print_to_stdout = False)))
@ -40,7 +40,7 @@ class handler(pykms_RpcBase.rpcBase):
response['pduData'] = responseData
ShellMessage.Process(17).run()
pretty_printer(num_text = 17, where = "srv")
response = byterize(response)
loggersrv.debug("RPC Message Response: \n%s\n" % justify(response.dump(print_to_stdout = False)))
loggersrv.debug("RPC Message Response Bytes: \n%s\n" % justify(deco(binascii.b2a_hex(enco(str(response), 'latin-1')), 'utf-8')))
@ -59,7 +59,7 @@ class handler(pykms_RpcBase.rpcBase):
request['alloc_hint'] = len(self.data)
request['pduData'] = str(self.data)
ShellMessage.Process(11).run()
pretty_printer(num_text = 11, where = "clt")
request = byterize(request)
loggersrv.debug("RPC Message Request: \n%s\n" % justify(request.dump(print_to_stdout = False)))
loggersrv.debug("RPC Message Request Bytes: \n%s\n" % justify(deco(binascii.b2a_hex(enco(str(request), 'latin-1')), 'utf-8')))

588
py-kms/pykms_Selectors.py Normal file
View File

@ -0,0 +1,588 @@
#!/usr/bin/env python3
"""
SPDX-License-Identifier: MIT
Backport of selectors.py from Python 3.5+ to support Python < 3.4
Also has the behavior specified in PEP 475 which is to retry syscalls
in the case of an EINTR error. This module is required because selectors34
does not follow this behavior and instead returns that no dile descriptor
events have occurred rather than retry the syscall. The decision to drop
support for select.devpoll is made to maintain 100% test coverage.
link: https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/python_modules/urllib3/util/selectors.py
"""
import errno
import math
import select
import socket
import sys
import time
from collections import namedtuple, Mapping
try:
monotonic = time.monotonic
except (AttributeError, ImportError): # Python 3.3<
from pykms_Time import monotonic
EVENT_READ = (1 << 0)
EVENT_WRITE = (1 << 1)
HAS_SELECT = True # Variable that shows whether the platform has a selector.
_SYSCALL_SENTINEL = object() # Sentinel in case a system call returns None.
_DEFAULT_SELECTOR = None
class SelectorError(Exception):
def __init__(self, errcode):
super(SelectorError, self).__init__()
self.errno = errcode
def __repr__(self):
return "<SelectorError errno={0}>".format(self.errno)
def __str__(self):
return self.__repr__()
def _fileobj_to_fd(fileobj):
""" Return a file descriptor from a file object. If
given an integer will simply return that integer back. """
if isinstance(fileobj, int):
fd = fileobj
else:
try:
fd = int(fileobj.fileno())
except (AttributeError, TypeError, ValueError):
raise ValueError("Invalid file object: {0!r}".format(fileobj))
if fd < 0:
raise ValueError("Invalid file descriptor: {0}".format(fd))
return fd
# Determine which function to use to wrap system calls because Python 3.5+
# already handles the case when system calls are interrupted.
if sys.version_info >= (3, 5):
def _syscall_wrapper(func, _, *args, **kwargs):
""" This is the short-circuit version of the below logic
because in Python 3.5+ all system calls automatically restart
and recalculate their timeouts. """
try:
return func(*args, **kwargs)
except (OSError, IOError, select.error) as e:
errcode = None
if hasattr(e, "errno"):
errcode = e.errno
raise SelectorError(errcode)
else:
def _syscall_wrapper(func, recalc_timeout, *args, **kwargs):
""" Wrapper function for syscalls that could fail due to EINTR.
All functions should be retried if there is time left in the timeout
in accordance with PEP 475. """
timeout = kwargs.get("timeout", None)
if timeout is None:
expires = None
recalc_timeout = False
else:
timeout = float(timeout)
if timeout < 0.0: # Timeout less than 0 treated as no timeout.
expires = None
else:
expires = monotonic() + timeout
args = list(args)
if recalc_timeout and "timeout" not in kwargs:
raise ValueError(
"Timeout must be in args or kwargs to be recalculated")
result = _SYSCALL_SENTINEL
while result is _SYSCALL_SENTINEL:
try:
result = func(*args, **kwargs)
# OSError is thrown by select.select
# IOError is thrown by select.epoll.poll
# select.error is thrown by select.poll.poll
# Aren't we thankful for Python 3.x rework for exceptions?
except (OSError, IOError, select.error) as e:
# select.error wasn't a subclass of OSError in the past.
errcode = None
if hasattr(e, "errno"):
errcode = e.errno
elif hasattr(e, "args"):
errcode = e.args[0]
# Also test for the Windows equivalent of EINTR.
is_interrupt = (errcode == errno.EINTR or (hasattr(errno, "WSAEINTR") and
errcode == errno.WSAEINTR))
if is_interrupt:
if expires is not None:
current_time = monotonic()
if current_time > expires:
raise OSError(errno=errno.ETIMEDOUT)
if recalc_timeout:
if "timeout" in kwargs:
kwargs["timeout"] = expires - current_time
continue
if errcode:
raise SelectorError(errcode)
else:
raise
return result
SelectorKey = namedtuple('SelectorKey', ['fileobj', 'fd', 'events', 'data'])
class _SelectorMapping(Mapping):
""" Mapping of file objects to selector keys """
def __init__(self, selector):
self._selector = selector
def __len__(self):
return len(self._selector._fd_to_key)
def __getitem__(self, fileobj):
try:
fd = self._selector._fileobj_lookup(fileobj)
return self._selector._fd_to_key[fd]
except KeyError:
raise KeyError("{0!r} is not registered.".format(fileobj))
def __iter__(self):
return iter(self._selector._fd_to_key)
class BaseSelector(object):
""" Abstract Selector class
A selector supports registering file objects to be monitored
for specific I/O events.
A file object is a file descriptor or any object with a
`fileno()` method. An arbitrary object can be attached to the
file object which can be used for example to store context info,
a callback, etc.
A selector can use various implementations (select(), poll(), epoll(),
and kqueue()) depending on the platform. The 'DefaultSelector' class uses
the most efficient implementation for the current platform.
"""
def __init__(self):
# Maps file descriptors to keys.
self._fd_to_key = {}
# Read-only mapping returned by get_map()
self._map = _SelectorMapping(self)
def _fileobj_lookup(self, fileobj):
""" Return a file descriptor from a file object.
This wraps _fileobj_to_fd() to do an exhaustive
search in case the object is invalid but we still
have it in our map. Used by unregister() so we can
unregister an object that was previously registered
even if it is closed. It is also used by _SelectorMapping
"""
try:
return _fileobj_to_fd(fileobj)
except ValueError:
# Search through all our mapped keys.
for key in self._fd_to_key.values():
if key.fileobj is fileobj:
return key.fd
# Raise ValueError after all.
raise
def register(self, fileobj, events, data=None):
""" Register a file object for a set of events to monitor. """
if (not events) or (events & ~(EVENT_READ | EVENT_WRITE)):
raise ValueError("Invalid events: {0!r}".format(events))
key = SelectorKey(fileobj, self._fileobj_lookup(fileobj), events, data)
if key.fd in self._fd_to_key:
raise KeyError("{0!r} (FD {1}) is already registered"
.format(fileobj, key.fd))
self._fd_to_key[key.fd] = key
return key
def unregister(self, fileobj):
""" Unregister a file object from being monitored. """
try:
key = self._fd_to_key.pop(self._fileobj_lookup(fileobj))
except KeyError:
raise KeyError("{0!r} is not registered".format(fileobj))
# Getting the fileno of a closed socket on Windows errors with EBADF.
except socket.error as e: # Platform-specific: Windows.
if e.errno != errno.EBADF:
raise
else:
for key in self._fd_to_key.values():
if key.fileobj is fileobj:
self._fd_to_key.pop(key.fd)
break
else:
raise KeyError("{0!r} is not registered".format(fileobj))
return key
def modify(self, fileobj, events, data=None):
""" Change a registered file object monitored events and data. """
# NOTE: Some subclasses optimize this operation even further.
try:
key = self._fd_to_key[self._fileobj_lookup(fileobj)]
except KeyError:
raise KeyError("{0!r} is not registered".format(fileobj))
if events != key.events:
self.unregister(fileobj)
key = self.register(fileobj, events, data)
elif data != key.data:
# Use a shortcut to update the data.
key = key._replace(data=data)
self._fd_to_key[key.fd] = key
return key
def select(self, timeout=None):
""" Perform the actual selection until some monitored file objects
are ready or the timeout expires. """
raise NotImplementedError()
def close(self):
""" Close the selector. This must be called to ensure that all
underlying resources are freed. """
self._fd_to_key.clear()
self._map = None
def get_key(self, fileobj):
""" Return the key associated with a registered file object. """
mapping = self.get_map()
if mapping is None:
raise RuntimeError("Selector is closed")
try:
return mapping[fileobj]
except KeyError:
raise KeyError("{0!r} is not registered".format(fileobj))
def get_map(self):
""" Return a mapping of file objects to selector keys """
return self._map
def _key_from_fd(self, fd):
""" Return the key associated to a given file descriptor
Return None if it is not found. """
try:
return self._fd_to_key[fd]
except KeyError:
return None
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
# Almost all platforms have select.select()
if hasattr(select, "select"):
class SelectSelector(BaseSelector):
""" Select-based selector. """
def __init__(self):
super(SelectSelector, self).__init__()
self._readers = set()
self._writers = set()
def register(self, fileobj, events, data=None):
key = super(SelectSelector, self).register(fileobj, events, data)
if events & EVENT_READ:
self._readers.add(key.fd)
if events & EVENT_WRITE:
self._writers.add(key.fd)
return key
def unregister(self, fileobj):
key = super(SelectSelector, self).unregister(fileobj)
self._readers.discard(key.fd)
self._writers.discard(key.fd)
return key
def _select(self, r, w, timeout=None):
""" Wrapper for select.select because timeout is a positional arg """
return select.select(r, w, [], timeout)
def select(self, timeout=None):
# Selecting on empty lists on Windows errors out.
if not len(self._readers) and not len(self._writers):
return []
timeout = None if timeout is None else max(timeout, 0.0)
ready = []
r, w, _ = _syscall_wrapper(self._select, True, self._readers,
self._writers, timeout)
r = set(r)
w = set(w)
for fd in r | w:
events = 0
if fd in r:
events |= EVENT_READ
if fd in w:
events |= EVENT_WRITE
key = self._key_from_fd(fd)
if key:
ready.append((key, events & key.events))
return ready
if hasattr(select, "poll"):
class PollSelector(BaseSelector):
""" Poll-based selector """
def __init__(self):
super(PollSelector, self).__init__()
self._poll = select.poll()
def register(self, fileobj, events, data=None):
key = super(PollSelector, self).register(fileobj, events, data)
event_mask = 0
if events & EVENT_READ:
event_mask |= select.POLLIN
if events & EVENT_WRITE:
event_mask |= select.POLLOUT
self._poll.register(key.fd, event_mask)
return key
def unregister(self, fileobj):
key = super(PollSelector, self).unregister(fileobj)
self._poll.unregister(key.fd)
return key
def _wrap_poll(self, timeout=None):
""" Wrapper function for select.poll.poll() so that
_syscall_wrapper can work with only seconds. """
if timeout is not None:
if timeout <= 0:
timeout = 0
else:
# select.poll.poll() has a resolution of 1 millisecond,
# round away from zero to wait *at least* timeout seconds.
timeout = math.ceil(timeout * 1e3)
result = self._poll.poll(timeout)
return result
def select(self, timeout=None):
ready = []
fd_events = _syscall_wrapper(self._wrap_poll, True, timeout=timeout)
for fd, event_mask in fd_events:
events = 0
if event_mask & ~select.POLLIN:
events |= EVENT_WRITE
if event_mask & ~select.POLLOUT:
events |= EVENT_READ
key = self._key_from_fd(fd)
if key:
ready.append((key, events & key.events))
return ready
if hasattr(select, "epoll"):
class EpollSelector(BaseSelector):
""" Epoll-based selector """
def __init__(self):
super(EpollSelector, self).__init__()
self._epoll = select.epoll()
def fileno(self):
return self._epoll.fileno()
def register(self, fileobj, events, data=None):
key = super(EpollSelector, self).register(fileobj, events, data)
events_mask = 0
if events & EVENT_READ:
events_mask |= select.EPOLLIN
if events & EVENT_WRITE:
events_mask |= select.EPOLLOUT
_syscall_wrapper(self._epoll.register, False, key.fd, events_mask)
return key
def unregister(self, fileobj):
key = super(EpollSelector, self).unregister(fileobj)
try:
_syscall_wrapper(self._epoll.unregister, False, key.fd)
except SelectorError:
# This can occur when the fd was closed since registry.
pass
return key
def select(self, timeout=None):
if timeout is not None:
if timeout <= 0:
timeout = 0.0
else:
# select.epoll.poll() has a resolution of 1 millisecond
# but luckily takes seconds so we don't need a wrapper
# like PollSelector. Just for better rounding.
timeout = math.ceil(timeout * 1e3) * 1e-3
timeout = float(timeout)
else:
timeout = -1.0 # epoll.poll() must have a float.
# We always want at least 1 to ensure that select can be called
# with no file descriptors registered. Otherwise will fail.
max_events = max(len(self._fd_to_key), 1)
ready = []
fd_events = _syscall_wrapper(self._epoll.poll, True,
timeout=timeout,
maxevents=max_events)
for fd, event_mask in fd_events:
events = 0
if event_mask & ~select.EPOLLIN:
events |= EVENT_WRITE
if event_mask & ~select.EPOLLOUT:
events |= EVENT_READ
key = self._key_from_fd(fd)
if key:
ready.append((key, events & key.events))
return ready
def close(self):
self._epoll.close()
super(EpollSelector, self).close()
if hasattr(select, "kqueue"):
class KqueueSelector(BaseSelector):
""" Kqueue / Kevent-based selector """
def __init__(self):
super(KqueueSelector, self).__init__()
self._kqueue = select.kqueue()
def fileno(self):
return self._kqueue.fileno()
def register(self, fileobj, events, data=None):
key = super(KqueueSelector, self).register(fileobj, events, data)
if events & EVENT_READ:
kevent = select.kevent(key.fd,
select.KQ_FILTER_READ,
select.KQ_EV_ADD)
_syscall_wrapper(self._kqueue.control, False, [kevent], 0, 0)
if events & EVENT_WRITE:
kevent = select.kevent(key.fd,
select.KQ_FILTER_WRITE,
select.KQ_EV_ADD)
_syscall_wrapper(self._kqueue.control, False, [kevent], 0, 0)
return key
def unregister(self, fileobj):
key = super(KqueueSelector, self).unregister(fileobj)
if key.events & EVENT_READ:
kevent = select.kevent(key.fd,
select.KQ_FILTER_READ,
select.KQ_EV_DELETE)
try:
_syscall_wrapper(self._kqueue.control, False, [kevent], 0, 0)
except SelectorError:
pass
if key.events & EVENT_WRITE:
kevent = select.kevent(key.fd,
select.KQ_FILTER_WRITE,
select.KQ_EV_DELETE)
try:
_syscall_wrapper(self._kqueue.control, False, [kevent], 0, 0)
except SelectorError:
pass
return key
def select(self, timeout=None):
if timeout is not None:
timeout = max(timeout, 0)
max_events = len(self._fd_to_key) * 2
ready_fds = {}
kevent_list = _syscall_wrapper(self._kqueue.control, True,
None, max_events, timeout)
for kevent in kevent_list:
fd = kevent.ident
event_mask = kevent.filter
events = 0
if event_mask == select.KQ_FILTER_READ:
events |= EVENT_READ
if event_mask == select.KQ_FILTER_WRITE:
events |= EVENT_WRITE
key = self._key_from_fd(fd)
if key:
if key.fd not in ready_fds:
ready_fds[key.fd] = (key, events & key.events)
else:
old_events = ready_fds[key.fd][1]
ready_fds[key.fd] = (key, (events | old_events) & key.events)
return list(ready_fds.values())
def close(self):
self._kqueue.close()
super(KqueueSelector, self).close()
if not hasattr(select, 'select'): # Platform-specific: AppEngine
HAS_SELECT = False
def _can_allocate(struct):
""" Checks that select structs can be allocated by the underlying
operating system, not just advertised by the select module. We don't
check select() because we'll be hopeful that most platforms that
don't have it available will not advertise it. (ie: GAE) """
try:
# select.poll() objects won't fail until used.
if struct == 'poll':
p = select.poll()
p.poll(0)
# All others will fail on allocation.
else:
getattr(select, struct)().close()
return True
except (OSError, AttributeError) as e:
return False
# Choose the best implementation, roughly:
# kqueue == epoll > poll > select. Devpoll not supported. (See above)
# select() also can't accept a FD > FD_SETSIZE (usually around 1024)
def DefaultSelector():
""" This function serves as a first call for DefaultSelector to
detect if the select module is being monkey-patched incorrectly
by eventlet, greenlet, and preserve proper behavior. """
global _DEFAULT_SELECTOR
if _DEFAULT_SELECTOR is None:
if _can_allocate('kqueue'):
_DEFAULT_SELECTOR = KqueueSelector
elif _can_allocate('epoll'):
_DEFAULT_SELECTOR = EpollSelector
elif _can_allocate('poll'):
_DEFAULT_SELECTOR = PollSelector
elif hasattr(select, 'select'):
_DEFAULT_SELECTOR = SelectSelector
else: # Platform-specific: AppEngine
raise ValueError('Platform does not have a selector')
return _DEFAULT_SELECTOR()

413
py-kms/pykms_Server.py Normal file → Executable file
View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import argparse
import binascii
import re
import sys
@ -8,26 +8,35 @@ import socket
import uuid
import logging
import os
import errno
import threading
import pickle
try:
# Python 2 import.
import SocketServer as socketserver
import Queue as Queue
import pykms_Selectors as selectors
from pykms_Time import monotonic as time
except ImportError:
# Python 3 import.
import socketserver
import queue as Queue
import selectors
from time import monotonic as time
import pykms_RpcBind, pykms_RpcRequest
from pykms_RpcBase import rpcBase
from pykms_Dcerpc import MSRPCHeader
from pykms_Misc import logger_create, check_logfile, check_lcid, pretty_errors
from pykms_Format import enco, deco, ShellMessage
from pykms_Misc import logger_create, check_logfile, check_lcid
from pykms_Misc import KmsParser, KmsException, KmsHelper
from pykms_Format import enco, deco, ShellMessage, pretty_printer
from Etrigan import Etrigan, Etrigan_parser, Etrigan_check, Etrigan_job
srv_description = 'KMS Server Emulator written in Python'
srv_version = 'py-kms_2019-05-15'
srv_version = "py-kms_2020-02-02"
__license__ = "The Unlicense"
__author__ = u"Matteo an <SystemRage@protonmail.com>"
__url__ = "https://github.com/SystemRage/py-kms"
srv_description = "py-kms: KMS Server Emulator written in Python"
srv_config = {}
##---------------------------------------------------------------------------------------------------------------------------------------------------------
@ -35,39 +44,118 @@ class KeyServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
daemon_threads = True
allow_reuse_address = True
def __init__(self, server_address, RequestHandlerClass):
socketserver.TCPServer.__init__(self, server_address, RequestHandlerClass)
self.__shutdown_request = False
self.r_service, self.w_service = os.pipe()
if hasattr(selectors, 'PollSelector'):
self._ServerSelector = selectors.PollSelector
else:
self._ServerSelector = selectors.SelectSelector
def pykms_serve(self):
""" Mixing of socketserver serve_forever() and handle_request() functions,
without elements blocking tkinter.
Handle one request at a time, possibly blocking.
Respects self.timeout.
"""
# Support people who used socket.settimeout() to escape
# pykms_serve() before self.timeout was available.
timeout = self.socket.gettimeout()
if timeout is None:
timeout = self.timeout
elif self.timeout is not None:
timeout = min(timeout, self.timeout)
if timeout is not None:
deadline = time() + timeout
try:
# Wait until a request arrives or the timeout expires.
with self._ServerSelector() as selector:
selector.register(fileobj = self, events = selectors.EVENT_READ)
# self-pipe trick.
selector.register(fileobj = self.r_service, events = selectors.EVENT_READ)
while not self.__shutdown_request:
ready = selector.select(timeout)
if self.__shutdown_request:
break
if ready == []:
if timeout is not None:
timeout = deadline - time()
if timeout < 0:
return self.handle_timeout()
else:
for key, mask in ready:
if key.fileobj is self:
self._handle_request_noblock()
elif key.fileobj is self.r_service:
# only to clean buffer.
msgkill = os.read(self.r_service, 8).decode('utf-8')
sys.exit(0)
finally:
self.__shutdown_request = False
def shutdown(self):
self.__shutdown_request = True
def handle_timeout(self):
pretty_errors(40, loggersrv)
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}Server connection timed out. Exiting...{end}")
def handle_error(self, request, client_address):
pass
class server_thread(threading.Thread):
def __init__(self):
def __init__(self, queue, name):
threading.Thread.__init__(self)
self.queue = serverqueue
self.is_running = False
self.daemon = True
self.name = name
self.queue = queue
self.server = None
self.is_running_server, self.with_gui, self.checked = [False for _ in range(3)]
self.is_running_thread = threading.Event()
def terminate_serve(self):
self.server.shutdown()
self.server.server_close()
self.server = None
self.is_running_server = False
def terminate_thread(self):
self.is_running_thread.set()
def terminate_eject(self):
os.write(self.server.w_service, u''.encode('utf-8'))
def run(self):
while True:
if not self.queue.empty():
item = self.queue.get()
if item == 'start':
self.is_running = True
# Check options.
server_check()
# Create and run threaded server.
self.server = server_create()
while not self.is_running_thread.is_set():
try:
while True:
self.server.handle_request()
except KeyboardInterrupt:
pass
finally:
self.server.server_close()
elif item == 'stop':
self.is_running = False
self.server = None
item = self.queue.get(block = True, timeout = 0.1)
self.queue.task_done()
except Queue.Empty:
continue
else:
try:
if item == 'start':
self.eject = False
self.is_running_server = True
# Check options.
if not self.checked:
server_check()
# Create and run server.
self.server = server_create()
self.server.pykms_serve()
except SystemExit as e:
self.eject = True
if not self.with_gui:
raise
else:
continue
##-----------------------------------------------------------------------------------------------------------------------------------------------
##---------------------------------------------------------------------------------------------------------------------------------------------------------
loggersrv = logging.getLogger('logsrv')
@ -94,53 +182,141 @@ The default is \"364F463A8863D35F\" or type \"RANDOM\" to auto generate the HWID
'choi' : ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "MINI"]},
'lfile' : {'help' : 'Use this option to set an output log file. The default is \"pykms_logserver.log\". Type \"STDOUT\" to view \
log info on stdout. Type \"FILESTDOUT\" to combine previous actions.',
'def' : os.path.dirname(os.path.abspath( __file__ )) + "/pykms_logserver.log", 'des' : "logfile"},
'def' : os.path.join(os.path.dirname(os.path.abspath(__file__)), 'pykms_logserver.log'), 'des' : "logfile"},
'lsize' : {'help' : 'Use this flag to set a maximum size (in MB) to the output log file. Desactivated by default.', 'def' : 0, 'des': "logsize"},
}
class KmsSrvException(Exception):
pass
class KmsSrvParser(argparse.ArgumentParser):
def error(self, message):
raise KmsSrvException(message)
def server_options():
parser = KmsSrvParser(description = srv_description, epilog = 'version: ' + srv_version)
parser.add_argument("ip", nargs = "?", action = "store", default = srv_options['ip']['def'], help = srv_options['ip']['help'], type = str)
parser.add_argument("port", nargs = "?", action = "store", default = srv_options['port']['def'], help = srv_options['port']['help'], type = int)
parser.add_argument("-e", "--epid", dest = srv_options['epid']['des'], default = srv_options['epid']['def'], help = srv_options['epid']['help'], type = str)
parser.add_argument("-l", "--lcid", dest = srv_options['lcid']['des'], default = srv_options['lcid']['def'], help = srv_options['lcid']['help'], type = int)
parser.add_argument("-c", "--client-count", dest = srv_options['count']['des'] , default = srv_options['count']['def'],
try:
server_parser = KmsParser(description = srv_description, epilog = 'version: ' + srv_version, add_help = False, allow_abbrev = False)
except TypeError:
server_parser = KmsParser(description = srv_description, epilog = 'version: ' + srv_version, add_help = False)
server_parser.add_argument("ip", nargs = "?", action = "store", default = srv_options['ip']['def'], help = srv_options['ip']['help'], type = str)
server_parser.add_argument("port", nargs = "?", action = "store", default = srv_options['port']['def'], help = srv_options['port']['help'], type = int)
server_parser.add_argument("-e", "--epid", action = "store", dest = srv_options['epid']['des'], default = srv_options['epid']['def'],
help = srv_options['epid']['help'], type = str)
server_parser.add_argument("-l", "--lcid", action = "store", dest = srv_options['lcid']['des'], default = srv_options['lcid']['def'],
help = srv_options['lcid']['help'], type = int)
server_parser.add_argument("-c", "--client-count", action = "store", dest = srv_options['count']['des'] , default = srv_options['count']['def'],
help = srv_options['count']['help'], type = int)
parser.add_argument("-a", "--activation-interval", dest = srv_options['activation']['des'], default = srv_options['activation']['def'],
help = srv_options['activation']['help'], type = int)
parser.add_argument("-r", "--renewal-interval", dest = srv_options['renewal']['des'], default = srv_options['renewal']['def'],
server_parser.add_argument("-a", "--activation-interval", action = "store", dest = srv_options['activation']['des'],
default = srv_options['activation']['def'], help = srv_options['activation']['help'], type = int)
server_parser.add_argument("-r", "--renewal-interval", action = "store", dest = srv_options['renewal']['des'], default = srv_options['renewal']['def'],
help = srv_options['renewal']['help'], type = int)
parser.add_argument("-s", "--sqlite", dest = srv_options['sql']['des'], action = "store_const", const = True, default = srv_options['sql']['def'],
server_parser.add_argument("-s", "--sqlite", action = "store_const", dest = srv_options['sql']['des'], const = True, default = srv_options['sql']['def'],
help = srv_options['sql']['help'])
parser.add_argument("-w", "--hwid", dest = srv_options['hwid']['des'], action = "store", default = srv_options['hwid']['def'],
server_parser.add_argument("-w", "--hwid", action = "store", dest = srv_options['hwid']['des'], default = srv_options['hwid']['def'],
help = srv_options['hwid']['help'], type = str)
parser.add_argument("-t", "--timeout", dest = srv_options['time']['des'], action = "store", default = srv_options['time']['def'],
server_parser.add_argument("-t", "--timeout", action = "store", dest = srv_options['time']['des'], default = srv_options['time']['def'],
help = srv_options['time']['help'], type = int)
parser.add_argument("-V", "--loglevel", dest = srv_options['llevel']['des'], action = "store", choices = srv_options['llevel']['choi'],
server_parser.add_argument("-V", "--loglevel", action = "store", dest = srv_options['llevel']['des'], choices = srv_options['llevel']['choi'],
default = srv_options['llevel']['def'], help = srv_options['llevel']['help'], type = str)
parser.add_argument("-F", "--logfile", nargs = "+", dest = srv_options['lfile']['des'], default = srv_options['lfile']['def'],
server_parser.add_argument("-F", "--logfile", nargs = "+", action = "store", dest = srv_options['lfile']['des'], default = srv_options['lfile']['def'],
help = srv_options['lfile']['help'], type = str)
parser.add_argument("-S", "--logsize", dest = srv_options['lsize']['des'], action = "store", default = srv_options['lsize']['def'],
server_parser.add_argument("-S", "--logsize", action = "store", dest = srv_options['lsize']['des'], default = srv_options['lsize']['def'],
help = srv_options['lsize']['help'], type = float)
server_parser.add_argument("-h", "--help", action = "help", help = "show this help message and exit")
try:
srv_config.update(vars(parser.parse_args()))
# Check logfile.
srv_config['logfile'] = check_logfile(srv_config['logfile'], srv_options['lfile']['def'], loggersrv)
except KmsSrvException as e:
pretty_errors(46, loggersrv, get_text = False, put_text = str(e), log_text = False)
daemon_parser = KmsParser(description = "daemon options inherited from Etrigan", add_help = False, allow_abbrev = False)
except TypeError:
daemon_parser = KmsParser(description = "daemon options inherited from Etrigan", add_help = False)
daemon_subparser = daemon_parser.add_subparsers(dest = "mode")
try:
etrigan_parser = daemon_subparser.add_parser("etrigan", add_help = False, allow_abbrev = False)
except TypeError:
etrigan_parser = daemon_subparser.add_parser("etrigan", add_help = False)
etrigan_parser.add_argument("-g", "--gui", action = "store_const", dest = 'gui', const = True, default = False,
help = "Enable py-kms GUI usage.")
etrigan_parser = Etrigan_parser(parser = etrigan_parser)
try:
if "-h" in sys.argv[1:]:
KmsHelper().printer(parsers = [server_parser, daemon_parser, etrigan_parser])
# Set defaults for config.
# case: python3 pykms_Server.py
srv_config.update(vars(server_parser.parse_args([])))
# Eventually set daemon values for config.
if 'etrigan' in sys.argv[1:]:
if 'etrigan' == sys.argv[1]:
# case: python3 pykms_Server.py etrigan start --daemon_optionals
srv_config.update(vars(daemon_parser.parse_args(sys.argv[1:])))
elif 'etrigan' == sys.argv[2]:
# case: python3 pykms_Server.py 1.2.3.4 etrigan start --daemon_optionals
srv_config['ip'] = sys.argv[1]
srv_config.update(vars(daemon_parser.parse_args(sys.argv[2:])))
else:
# case: python3 pykms_Server.py 1.2.3.4 1234 --main_optionals etrigan start --daemon_optionals
knw_args, knw_extras = server_parser.parse_known_args()
# fix for logfile option (at the end) that catchs etrigan parser options.
if 'etrigan' in knw_args.logfile:
indx = knw_args.logfile.index('etrigan')
for num, elem in enumerate(knw_args.logfile[indx:]):
knw_extras.insert(num, elem)
knw_args.logfile = knw_args.logfile[:indx]
# continue parsing.
if len(knw_extras) > 0 and knw_extras[0] in ['etrigan']:
daemon_parser.parse_args(knw_extras, namespace = knw_args)
srv_config.update(vars(knw_args))
else:
# Update dict config.
# case: python3 pykms_Server.py 1.2.3.4 1234 --main_optionals
knw_args, knw_extras = server_parser.parse_known_args()
if knw_extras != []:
raise KmsException("unrecognized arguments: %s" %' '.join(knw_extras))
else:
srv_config.update(vars(knw_args))
except KmsException as e:
pretty_printer(put_text = "{reverse}{red}{bold}%s. Exiting...{end}" %str(e), to_exit = True)
def server_daemon():
if 'etrigan' in srv_config.values():
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'pykms_config.pickle')
if srv_config['operation'] in ['stop', 'restart', 'status'] and len(sys.argv[1:]) > 2:
pretty_printer(put_text = "{reverse}{red}{bold}too much arguments. Exiting...{end}", to_exit = True)
if srv_config['gui']:
pass
else:
if srv_config['operation'] == 'start':
with open(path, 'wb') as file:
pickle.dump(srv_config, file, protocol = pickle.HIGHEST_PROTOCOL)
elif srv_config['operation'] in ['stop', 'status', 'restart']:
with open(path, 'rb') as file:
old_srv_config = pickle.load(file)
old_srv_config = {x: old_srv_config[x] for x in old_srv_config if x not in ['operation']}
srv_config.update(old_srv_config)
serverdaemon = Etrigan(srv_config['etriganpid'],
logfile = srv_config['etriganlog'], loglevel = srv_config['etriganlev'],
mute = srv_config['etriganmute'], pause_loop = None)
if srv_config['operation'] == 'start':
serverdaemon.want_quit = True
if srv_config['gui']:
serverdaemon.funcs_to_daemonize = [server_with_gui]
else:
server_without_gui = ServerWithoutGui()
serverdaemon.funcs_to_daemonize = [server_without_gui.start, server_without_gui.join]
indx_for_clean = lambda: (0, )
serverdaemon.quit_on_stop = [indx_for_clean, server_without_gui.clean]
Etrigan_job(srv_config['operation'], serverdaemon)
def server_check():
# Check logfile.
srv_config['logfile'] = check_logfile(srv_config['logfile'], srv_options['lfile']['def'], where = "srv")
# Setup hidden or not messages.
ShellMessage.view = ( False if any(i in ['STDOUT', 'FILESTDOUT'] for i in srv_config['logfile']) else True )
# Create log.
logger_create(loggersrv, srv_config, mode = 'a')
@ -155,33 +331,40 @@ def server_check():
diff = set(hexstr).symmetric_difference(set(hexsub))
if len(diff) != 0:
pretty_errors(41, loggersrv, put_text = [hexstr.upper(), diff])
diff = str(diff).replace('{', '').replace('}', '')
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}HWID '%s' is invalid. Digit %s non hexadecimal. Exiting...{end}" %(hexstr.upper(), diff))
else:
lh = len(hexsub)
if lh % 2 != 0:
pretty_errors(42, loggersrv, put_text = hexsub.upper())
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}HWID '%s' is invalid. Hex string is odd length. Exiting...{end}" %hexsub.upper())
elif lh < 16:
pretty_errors(43, loggersrv, put_text = hexsub.upper())
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}HWID '%s' is invalid. Hex string is too short. Exiting...{end}" %hexsub.upper())
elif lh > 16:
pretty_errors(44, loggersrv, put_text = hexsub.upper())
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}HWID '%s' is invalid. Hex string is too long. Exiting...{end}" %hexsub.upper())
else:
srv_config['hwid'] = binascii.a2b_hex(hexsub)
# Check LCID.
srv_config['lcid'] = check_lcid(srv_config['lcid'], loggersrv)
srv_config['lcid'] = check_lcid(srv_config['lcid'], loggersrv.warning)
# Check sqlite.
try:
import sqlite3
except:
loggersrv.warning("Module \"sqlite3\" is not installed, database support disabled.")
pretty_printer(log_obj = loggersrv.warning,
put_text = "{reverse}{yellow}{bold}Module 'sqlite3' is not installed, database support disabled.{end}")
srv_config['dbSupport'] = False
else:
srv_config['dbSupport'] = True
# Check port.
if not 1 <= srv_config['port'] <= 65535:
pretty_errors(45, loggersrv, put_text = srv_config['port'])
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{red}{bold}Port number '%s' is invalid. Enter between 1 - 65535. Exiting...{end}" %srv_config['port'])
def server_create():
server = KeyServer((srv_config['ip'], srv_config['port']), kmsServerHandler)
@ -190,16 +373,59 @@ def server_create():
loggersrv.info("HWID: %s" % deco(binascii.b2a_hex(srv_config['hwid']), 'utf-8').upper())
return server
def srv_main_without_gui():
def server_terminate(generic_srv, exit_server = False, exit_thread = False):
if exit_server:
generic_srv.terminate_serve()
if exit_thread:
generic_srv.terminate_thread()
class ServerWithoutGui(object):
def start(self):
import queue as Queue
daemon_queue = Queue.Queue(maxsize = 0)
daemon_serverthread = server_thread(daemon_queue, name = "Thread-Srv-Daemon")
daemon_serverthread.setDaemon(True)
# options already checked in `server_main_terminal`.
daemon_serverthread.checked = True
daemon_serverthread.start()
daemon_queue.put('start')
return 0, daemon_serverthread
def join(self, daemon_serverthread):
while daemon_serverthread.is_alive():
daemon_serverthread.join(timeout = 0.5)
def clean(self, daemon_serverthread):
server_terminate(daemon_serverthread, exit_server = True, exit_thread = True)
def server_main_terminal():
# Parse options.
server_options()
# Check options.
server_check()
serverthread.checked = True
if 'etrigan' not in srv_config.values():
# (without GUI) and (without daemon).
# Run threaded server.
serverqueue.put('start')
serverthread.join()
# Wait to finish.
try:
while serverthread.is_alive():
serverthread.join(timeout = 0.5)
except (KeyboardInterrupt, SystemExit):
server_terminate(serverthread, exit_server = True, exit_thread = True)
else:
# (with or without GUI) and (with daemon)
# Setup daemon (eventually).
server_daemon()
def srv_main_with_gui(width = 950, height = 660):
def server_with_gui():
import pykms_GuiBase
width = 950
height = 660
root = pykms_GuiBase.KmsGui()
root.title(pykms_GuiBase.gui_description + ' ' + pykms_GuiBase.gui_version)
# Main window initial position.
@ -209,9 +435,14 @@ def srv_main_with_gui(width = 950, height = 660):
x = (ws / 2) - (width / 2)
y = (hs / 2) - (height / 2)
root.geometry('+%d+%d' %(x, y))
# disable maximize button.
root.resizable(0, 0)
root.mainloop()
def server_main_no_terminal():
# Run tkinter GUI.
# (with GUI) and (without daemon).
server_with_gui()
class kmsServerHandler(socketserver.BaseRequestHandler):
def setup(self):
@ -222,38 +453,45 @@ class kmsServerHandler(socketserver.BaseRequestHandler):
# self.request is the TCP socket connected to the client
try:
self.data = self.request.recv(1024)
except socket.error as e:
if e.errno == errno.ECONNRESET:
loggersrv.error("Connection reset by peer.")
break
else:
raise
if self.data == '' or not self.data:
loggersrv.warning("No data received !")
pretty_printer(log_obj = loggersrv.warning,
put_text = "{reverse}{yellow}{bold}No data received.{end}")
break
except socket.error as e:
pretty_printer(log_obj = loggersrv.error,
put_text = "{reverse}{red}{bold}While receiving: %s{end}" %str(e))
break
packetType = MSRPCHeader(self.data)['type']
if packetType == rpcBase.packetType['bindReq']:
loggersrv.info("RPC bind request received.")
ShellMessage.Process([-2, 2]).run()
pretty_printer(num_text = [-2, 2], where = "srv")
handler = pykms_RpcBind.handler(self.data, srv_config)
elif packetType == rpcBase.packetType['request']:
loggersrv.info("Received activation request.")
ShellMessage.Process([-2, 13]).run()
pretty_printer(num_text = [-2, 13], where = "srv")
handler = pykms_RpcRequest.handler(self.data, srv_config)
else:
loggersrv.error("Invalid RPC request type ", packetType)
pretty_printer(log_obj = loggersrv.error,
put_text = "{reverse}{red}{bold}Invalid RPC request type %s.{end}" %packetType)
break
res = enco(str(handler.populate()), 'latin-1')
self.request.send(res)
if packetType == rpcBase.packetType['bindReq']:
loggersrv.info("RPC bind acknowledged.")
ShellMessage.Process([-3, 5, 6]).run()
pretty_printer(num_text = [-3, 5, 6], where = "srv")
elif packetType == rpcBase.packetType['request']:
loggersrv.info("Responded to activation request.")
ShellMessage.Process([-3, 18, 19]).run()
pretty_printer(num_text = [-3, 18, 19], where = "srv")
try:
self.request.send(res)
if packetType == rpcBase.packetType['request']:
break
except socket.error as e:
pretty_printer(log_obj = loggersrv.error,
put_text = "{reverse}{red}{bold}While sending: %s{end}" %str(e))
break
def finish(self):
@ -262,14 +500,15 @@ class kmsServerHandler(socketserver.BaseRequestHandler):
serverqueue = Queue.Queue(maxsize = 0)
serverthread = server_thread()
serverthread = server_thread(serverqueue, name = "Thread-Srv")
serverthread.setDaemon(True)
serverthread.start()
if __name__ == "__main__":
if sys.stdout.isatty():
srv_main_without_gui()
server_main_terminal()
else:
try:
srv_main_with_gui()
server_main_no_terminal()
except:
srv_main_without_gui()
server_main_terminal()

View File

@ -2,7 +2,6 @@
import os
import logging
import sys
# sqlite3 is optional.
try:
@ -10,6 +9,8 @@ try:
except ImportError:
pass
from pykms_Format import pretty_printer
#--------------------------------------------------------------------------------------------------------------------------------------------------------
loggersrv = logging.getLogger('logsrv')
@ -26,8 +27,8 @@ def sql_initialize():
licenseStatus TEXT, lastRequestTime INTEGER, kmsEpid TEXT, requestCount INTEGER)")
except sqlite3.Error as e:
loggersrv.error("Error %s:" % e.args[0])
sys.exit(1)
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}%s. Exiting...{end}" %str(e))
finally:
if con:
con.commit()
@ -63,11 +64,11 @@ skuId, licenseStatus, lastRequestTime, requestCount) VALUES (:clientMachineId, :
cur.execute("UPDATE clients SET requestCount=requestCount+1 WHERE clientMachineId=:clientMachineId;", infoDict)
except sqlite3.Error as e:
loggersrv.error("Error %s:" % e.args[0])
sys.exit(1)
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}%s. Exiting...{end}" %str(e))
except sqlite3.Error as e:
loggersrv.error("Error %s:" % e.args[0])
sys.exit(1)
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}%s. Exiting...{end}" %str(e))
finally:
if con:
con.commit()
@ -88,11 +89,11 @@ def sql_update_epid(dbName, kmsRequest, response):
cur.execute("UPDATE clients SET kmsEpid=? WHERE clientMachineId=?;", (str(response["kmsEpid"].decode('utf-16le')),
cmid))
except sqlite3.Error as e:
loggersrv.error("Error %s:" % e.args[0])
sys.exit(1)
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}%s. Exiting...{end}" %str(e))
except sqlite3.Error as e:
loggersrv.error("Error %s:" % e.args[0])
sys.exit(1)
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}%s. Exiting...{end}" %str(e))
finally:
if con:
con.commit()

174
py-kms/pykms_Time.py Normal file
View File

@ -0,0 +1,174 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
monotonic
~~~~~~~~~
This module provides a ``monotonic()`` function which returns the
value (in fractional seconds) of a clock which never goes backwards.
On Python 3.3 or newer, ``monotonic`` will be an alias of
``time.monotonic`` from the standard library. On older versions,
it will fall back to an equivalent implementation:
+-------------+----------------------------------------+
| Linux, BSD | ``clock_gettime(3)`` |
+-------------+----------------------------------------+
| Windows | ``GetTickCount`` or ``GetTickCount64`` |
+-------------+----------------------------------------+
| OS X | ``mach_absolute_time`` |
+-------------+----------------------------------------+
If no suitable implementation exists for the current platform,
attempting to import this module (or to import from it) will
cause a ``RuntimeError`` exception to be raised.
Copyright 2014, 2015, 2016 Ori Livneh <ori@wikimedia.org>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
link: https://github.com/atdt/monotonic/blob/master/monotonic.py
"""
import time
__all__ = ('monotonic',)
try:
monotonic = time.monotonic
except AttributeError:
import ctypes
import ctypes.util
import os
import sys
import threading
try:
if sys.platform == 'darwin': # OS X, iOS
# See Technical Q&A QA1398 of the Mac Developer Library:
# <https://developer.apple.com/library/mac/qa/qa1398/>
libc = ctypes.CDLL('/usr/lib/libc.dylib', use_errno=True)
class mach_timebase_info_data_t(ctypes.Structure):
"""System timebase info. Defined in <mach/mach_time.h>."""
_fields_ = (('numer', ctypes.c_uint32),
('denom', ctypes.c_uint32))
mach_absolute_time = libc.mach_absolute_time
mach_absolute_time.restype = ctypes.c_uint64
timebase = mach_timebase_info_data_t()
libc.mach_timebase_info(ctypes.byref(timebase))
ticks_per_second = timebase.numer / timebase.denom * 1.0e9
def monotonic():
"""Monotonic clock, cannot go backward."""
return mach_absolute_time() / ticks_per_second
elif sys.platform.startswith('win32') or sys.platform.startswith('cygwin'):
if sys.platform.startswith('cygwin'):
# Note: cygwin implements clock_gettime (CLOCK_MONOTONIC = 4) since
# version 1.7.6. Using raw WinAPI for maximum version compatibility.
# Ugly hack using the wrong calling convention (in 32-bit mode)
# because ctypes has no windll under cygwin (and it also seems that
# the code letting you select stdcall in _ctypes doesn't exist under
# the preprocessor definitions relevant to cygwin).
# This is 'safe' because:
# 1. The ABI of GetTickCount and GetTickCount64 is identical for
# both calling conventions because they both have no parameters.
# 2. libffi masks the problem because after making the call it doesn't
# touch anything through esp and epilogue code restores a correct
# esp from ebp afterwards.
try:
kernel32 = ctypes.cdll.kernel32
except OSError: # 'No such file or directory'
kernel32 = ctypes.cdll.LoadLibrary('kernel32.dll')
else:
kernel32 = ctypes.windll.kernel32
GetTickCount64 = getattr(kernel32, 'GetTickCount64', None)
if GetTickCount64:
# Windows Vista / Windows Server 2008 or newer.
GetTickCount64.restype = ctypes.c_ulonglong
def monotonic():
"""Monotonic clock, cannot go backward."""
return GetTickCount64() / 1000.0
else:
# Before Windows Vista.
GetTickCount = kernel32.GetTickCount
GetTickCount.restype = ctypes.c_uint32
get_tick_count_lock = threading.Lock()
get_tick_count_last_sample = 0
get_tick_count_wraparounds = 0
def monotonic():
"""Monotonic clock, cannot go backward."""
global get_tick_count_last_sample
global get_tick_count_wraparounds
with get_tick_count_lock:
current_sample = GetTickCount()
if current_sample < get_tick_count_last_sample:
get_tick_count_wraparounds += 1
get_tick_count_last_sample = current_sample
final_milliseconds = get_tick_count_wraparounds << 32
final_milliseconds += get_tick_count_last_sample
return final_milliseconds / 1000.0
else:
try:
clock_gettime = ctypes.CDLL(ctypes.util.find_library('c'),
use_errno=True).clock_gettime
except Exception:
clock_gettime = ctypes.CDLL(ctypes.util.find_library('rt'),
use_errno=True).clock_gettime
class timespec(ctypes.Structure):
"""Time specification, as described in clock_gettime(3)."""
_fields_ = (('tv_sec', ctypes.c_long),
('tv_nsec', ctypes.c_long))
if sys.platform.startswith('linux'):
CLOCK_MONOTONIC = 1
elif sys.platform.startswith('freebsd'):
CLOCK_MONOTONIC = 4
elif sys.platform.startswith('sunos5'):
CLOCK_MONOTONIC = 4
elif 'bsd' in sys.platform:
CLOCK_MONOTONIC = 3
elif sys.platform.startswith('aix'):
CLOCK_MONOTONIC = ctypes.c_longlong(10)
def monotonic():
"""Monotonic clock, cannot go backward."""
ts = timespec()
if clock_gettime(CLOCK_MONOTONIC, ctypes.pointer(ts)):
errno = ctypes.get_errno()
raise OSError(errno, os.strerror(errno))
return ts.tv_sec + ts.tv_nsec / 1.0e9
# Perform a sanity-check.
if monotonic() - monotonic() > 0:
raise ValueError('monotonic() is not monotonic!')
except Exception as e:
raise RuntimeError('no suitable implementation for this system: ' + repr(e))