Support for multi-address connection

This commit is contained in:
Matteo ℱan 2020-09-21 00:29:26 +02:00
parent 016a4c367f
commit 990cd5e48f
No known key found for this signature in database
GPG Key ID: 3C30A05BC133D9B6
3 changed files with 493 additions and 102 deletions

215
py-kms/pykms_Connect.py Normal file
View File

@ -0,0 +1,215 @@
#!/usr/bin/env python3
import os
import socket
import selectors
import ipaddress
# https://github.com/python/cpython/blob/master/Lib/socket.py
def has_dualstack_ipv6():
""" Return True if the platform supports creating a SOCK_STREAM socket
which can handle both AF_INET and AF_INET6 (IPv4 / IPv6) connections.
"""
if not socket.has_ipv6 or not hasattr(socket._socket, 'IPPROTO_IPV6') or not hasattr(socket._socket, 'IPV6_V6ONLY'):
return False
try:
with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as sock:
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
return True
except socket.error:
return False
def create_server_sock(address, *, family = socket.AF_INET, backlog = None, reuse_port = False, dualstack_ipv6 = False):
""" Convenience function which creates a SOCK_STREAM type socket
bound to *address* (a 2-tuple (host, port)) and return the socket object.
Internally it takes care of choosing the right address family (IPv4 or IPv6),depending on
the host specified in *address* tuple.
*family* should be either AF_INET or AF_INET6.
*backlog* is the queue size passed to socket.listen().
*reuse_port* dictates whether to use the SO_REUSEPORT socket option.
*dualstack_ipv6* if True and the platform supports it, it will create an AF_INET6 socket able to accept both IPv4 or IPv6 connections;
when False it will explicitly disable this option on platforms that enable it by default (e.g. Linux).
"""
if reuse_port and not hasattr(socket._socket, "SO_REUSEPORT"):
raise ValueError("SO_REUSEPORT not supported on this platform")
if dualstack_ipv6:
if not has_dualstack_ipv6():
raise ValueError("dualstack_ipv6 not supported on this platform")
if family != socket.AF_INET6:
raise ValueError("dualstack_ipv6 requires AF_INET6 family")
sock = socket.socket(family, socket.SOCK_STREAM)
try:
# Note about Windows. We don't set SO_REUSEADDR because:
# 1) It's unnecessary: bind() will succeed even in case of a
# previous closed socket on the same address and still in
# TIME_WAIT state.
# 2) If set, another socket is free to bind() on the same
# address, effectively preventing this one from accepting
# connections. Also, it may set the process in a state where
# it'll no longer respond to any signals or graceful kills.
# See: msdn2.microsoft.com/en-us/library/ms740621(VS.85).aspx
if os.name not in ('nt', 'cygwin') and hasattr(socket._socket, 'SO_REUSEADDR'):
try:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
except socket.error:
# Fail later on bind(), for platforms which may not
# support this option.
pass
if reuse_port:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
if socket.has_ipv6 and family == socket.AF_INET6:
if dualstack_ipv6:
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
elif hasattr(socket._socket, "IPV6_V6ONLY") and hasattr(socket._socket, "IPPROTO_IPV6"):
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
try:
sock.bind(address)
except socket.error as err:
msg = '%s (while attempting to bind on address %r)' %(err.strerror, address)
raise socket.error(err.errno, msg) from None
if backlog is None:
sock.listen()
else:
sock.listen(backlog)
return sock
except socket.error:
sock.close()
raise
# Giampaolo Rodola' class (license MIT) revisited for py-kms.
# http://code.activestate.com/recipes/578504-server-supporting-ipv4-and-ipv6/
class MultipleListener(object):
""" Listen on multiple addresses specified as a list of
(`host`, `port`, `backlog`, `reuse_port`) tuples.
Useful to listen on both IPv4 and IPv6 on those systems where a dual stack
is not supported natively (Windows and many UNIXes).
Calls like settimeout() and setsockopt() will be applied to all sockets.
Calls like gettimeout() or getsockopt() will refer to the first socket in the list.
"""
def __init__(self, addresses = [], want_dual = False):
self.socks, self.sockmap = [], {}
completed = False
self.cant_dual = []
try:
for addr in addresses:
addr = self.check(addr)
ip_ver = ipaddress.ip_address(addr[0])
if ip_ver.version == 4 and want_dual:
self.cant_dual.append(addr[0])
sock = create_server_sock((addr[0], addr[1]),
family = (socket.AF_INET if ip_ver.version == 4 else socket.AF_INET6),
backlog = addr[2],
reuse_port = addr[3],
dualstack_ipv6 = (False if ip_ver.version == 4 else want_dual))
self.socks.append(sock)
self.sockmap[sock.fileno()] = sock
completed = True
finally:
if not completed:
self.close()
def __enter__(self):
return self
def __exit__(self):
self.close()
def __repr__(self):
addrs = []
for sock in self.socks:
try:
addrs.append(sock.getsockname())
except socket.error:
addrs.append(())
return "<%s(%r) at %#x>" %(self.__class__.__name__, addrs, id(self))
def filenos(self):
""" Return sockets' file descriptors as a list of integers. """
return list(self.sockmap.keys())
def register(self, pollster):
for fd in self.filenos():
pollster.register(fileobj = fd, events = selectors.EVENT_READ)
def multicall(self, name, *args, **kwargs):
for sock in self.socks:
meth = getattr(sock, name)
meth(*args, **kwargs)
def poll(self):
""" Return the first readable fd. """
if hasattr(selectors, 'PollSelector'):
pollster = selectors.PollSelector
else:
pollster = selectors.SelectSelector
timeout = self.gettimeout()
with pollster() as pollster:
self.register(pollster)
fds = pollster.select(timeout)
if timeout and fds == []:
raise socket.timeout('timed out')
try:
return fds[0][0].fd
except IndexError:
# non-blocking socket
pass
def accept(self):
""" Accept a connection from the first socket which is ready to do so. """
fd = self.poll()
sock = (self.sockmap[fd] if fd else self.socks[0])
return sock.accept()
def getsockname(self):
""" Return first registered socket's own address. """
return self.socks[0].getsockname()
def getsockopt(self, level, optname, buflen = 0):
""" Return first registered socket's options. """
return self.socks[0].getsockopt(level, optname, buflen)
def gettimeout(self):
""" Return first registered socket's timeout. """
return self.socks[0].gettimeout()
def settimeout(self, timeout):
""" Set timeout for all registered sockets. """
self.multicall('settimeout', timeout)
def setblocking(self, flag):
""" Set non-blocking mode for all registered sockets. """
self.multicall('setblocking', flag)
def setsockopt(self, level, optname, value):
""" Set option for all registered sockets. """
self.multicall('setsockopt', level, optname, value)
def shutdown(self, how):
""" Shut down all registered sockets. """
self.multicall('shutdown', how)
def close(self):
""" Close all registered sockets. """
self.multicall('close')
self.socks, self.sockmap = [], {}
def check(self, address):
if len(address) == 1:
raise socket.error("missing `host` or `port` parameter.")
if len(address) == 2:
address += (None, True,)
elif len(address) == 3:
address += (True,)
return address

View File

@ -338,22 +338,32 @@ class KmsParserHelp(object):
return help_list return help_list
def printer(self, parsers): 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] parser_base = parsers[0]
if len(parsers) == 1:
replace_epilog_with = '' replace_epilog_with = ''
else:
parser_adj_0, parser_sub_0 = parsers[1]
replace_epilog_with = 80 * '*' + '\n'
if len(parsers) == 3:
parser_adj_1, parser_sub_1 = parsers[2]
print('\n' + parser_base.description) print('\n' + parser_base.description)
print(len(parser_base.description) * '-' + '\n') print(len(parser_base.description) * '-' + '\n')
for line in self.replace(parser_base, replace_epilog_with): for line in self.replace(parser_base, replace_epilog_with):
print(line) print(line)
try:
print(parser_adj.description + '\n') def subprinter(adj, sub, replace):
for line in self.replace(parser_sub, replace_epilog_with): print(adj.description + '\n')
for line in self.replace(sub, replace):
print(line) print(line)
except: print('\n')
pass
if len(parsers) >= 2:
subprinter(parser_adj_0, parser_sub_0, replace_epilog_with)
if len(parsers) == 3:
print(replace_epilog_with)
subprinter(parser_adj_1, parser_sub_1, replace_epilog_with)
print('\n' + len(parser_base.epilog) * '-') print('\n' + len(parser_base.epilog) * '-')
print(parser_base.epilog + '\n') print(parser_base.epilog + '\n')
parser_base.exit() parser_base.exit()
@ -363,13 +373,13 @@ def kms_parser_get(parser):
act = vars(parser)['_actions'] act = vars(parser)['_actions']
for i in range(len(act)): for i in range(len(act)):
if act[i].option_strings not in ([], ['-h', '--help']): if act[i].option_strings not in ([], ['-h', '--help']):
if isinstance(act[i], argparse._StoreAction): if isinstance(act[i], argparse._StoreAction) or isinstance(act[i], argparse._AppendAction):
onearg.append(act[i].option_strings) onearg.append(act[i].option_strings)
else: else:
zeroarg.append(act[i].option_strings) zeroarg.append(act[i].option_strings)
return zeroarg, onearg return zeroarg, onearg
def kms_parser_check_optionals(userarg, zeroarg, onearg, msg = 'optional py-kms server', exclude_opt_len = []): def kms_parser_check_optionals(userarg, zeroarg, onearg, msg = 'optional py-kms server', exclude_opt_len = [], exclude_opt_dup = []):
""" """
For optionals arguments: For optionals arguments:
Don't allow duplicates, Don't allow duplicates,
@ -399,12 +409,13 @@ def kms_parser_check_optionals(userarg, zeroarg, onearg, msg = 'optional py-kms
# Check duplicates. # Check duplicates.
founds = [i for i in userarg if i in allarg] founds = [i for i in userarg if i in allarg]
dup = [item for item in set(founds) if founds.count(item) > 1] dup = [item for item in set(founds) if founds.count(item) > 1]
if dup != []: for d in dup:
if d not in exclude_opt_dup:
raise KmsParserException("%s argument `%s` appears several times" %(msg, ', '.join(dup))) raise KmsParserException("%s argument `%s` appears several times" %(msg, ', '.join(dup)))
# Check length. # Check length.
elem = None elem = None
for found in founds: for found in set(founds):
if found not in exclude_opt_len: if found not in exclude_opt_len:
pos = userarg.index(found) pos = userarg.index(found)
try: try:
@ -433,6 +444,70 @@ def kms_parser_check_positionals(config, parse_method, arguments = [], force_par
else: else:
raise KmsParserException("unrecognized %s arguments: '%s'" %(msg, e.split(': ')[1])) raise KmsParserException("unrecognized %s arguments: '%s'" %(msg, e.split(': ')[1]))
def kms_parser_check_connect(config, options, userarg, zeroarg, onearg):
if 'listen' in config:
try:
lung = len(config['listen'])
except TypeError:
raise KmsParserException("optional connect arguments missing")
rng = range(lung - 1)
config['backlog_primary'] = options['backlog']['def']
config['reuse_primary'] = options['reuse']['def']
def assign(arguments, index, options, config, default, islast = False):
if all(opt not in arguments for opt in options):
if config and islast:
config.append(default)
elif config:
config.insert(index, default)
else:
config.append(default)
def assign_primary(arguments, config):
if any(opt in arguments for opt in ['-b', '--backlog']):
config['backlog_primary'] = config['backlog'][0]
config['backlog'].pop(0)
if any(opt in arguments for opt in ['-u', '--no-reuse']):
config['reuse_primary'] = config['reuse'][0]
config['reuse'].pop(0)
if config['listen']:
# check before.
pos = userarg.index(config['listen'][0])
assign_primary(userarg[1 : pos - 1], config)
# check middle.
for indx in rng:
pos1 = userarg.index(config['listen'][indx])
pos2 = userarg.index(config['listen'][indx + 1])
arguments = userarg[pos1 + 1 : pos2 - 1]
kms_parser_check_optionals(arguments, zeroarg, onearg, msg = 'optional connect')
assign(arguments, indx, ['-b', '--backlog'], config['backlog'], options['backlog']['def'])
assign(arguments, indx, ['-u', '--no-reuse'], config['reuse'], options['reuse']['def'])
if not arguments:
config['backlog'][indx] = config['backlog_primary']
config['reuse'][indx] = config['reuse_primary']
# check after.
if lung == 1:
indx = -1
pos = userarg.index(config['listen'][indx + 1])
arguments = userarg[pos + 1:]
kms_parser_check_optionals(arguments, zeroarg, onearg, msg = 'optional connect')
assign(arguments, None, ['-b', '--backlog'], config['backlog'], options['backlog']['def'], islast = True)
assign(arguments, None, ['-u', '--no-reuse'], config['reuse'], options['reuse']['def'], islast = True)
if not arguments:
config['backlog'][indx + 1] = config['backlog_primary']
config['reuse'][indx + 1] = config['reuse_primary']
else:
assign_primary(userarg[1:], config)
#------------------------------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------------------------------
def proper_none(dictionary): def proper_none(dictionary):
for key in dictionary.keys(): for key in dictionary.keys():

View File

@ -14,16 +14,16 @@ import socketserver
import queue as Queue import queue as Queue
import selectors import selectors
from time import monotonic as time from time import monotonic as time
import ipaddress
import pykms_RpcBind, pykms_RpcRequest import pykms_RpcBind, pykms_RpcRequest
from pykms_RpcBase import rpcBase from pykms_RpcBase import rpcBase
from pykms_Dcerpc import MSRPCHeader from pykms_Dcerpc import MSRPCHeader
from pykms_Misc import check_setup, check_lcid, check_dir from pykms_Misc import check_setup, check_lcid, check_dir
from pykms_Misc import KmsParser, KmsParserException, KmsParserHelp from pykms_Misc import KmsParser, KmsParserException, KmsParserHelp
from pykms_Misc import kms_parser_get, kms_parser_check_optionals, kms_parser_check_positionals from pykms_Misc import kms_parser_get, kms_parser_check_optionals, kms_parser_check_positionals, kms_parser_check_connect
from pykms_Format import enco, deco, pretty_printer from pykms_Format import enco, deco, pretty_printer, justify
from Etrigan import Etrigan, Etrigan_parser, Etrigan_check, Etrigan_job from Etrigan import Etrigan, Etrigan_parser, Etrigan_check, Etrigan_job
from pykms_Connect import MultipleListener
srv_version = "py-kms_2020-07-01" srv_version = "py-kms_2020-07-01"
__license__ = "The Unlicense" __license__ = "The Unlicense"
@ -35,9 +35,8 @@ srv_config = {}
##--------------------------------------------------------------------------------------------------------------------------------------------------------- ##---------------------------------------------------------------------------------------------------------------------------------------------------------
class KeyServer(socketserver.ThreadingMixIn, socketserver.TCPServer): class KeyServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
daemon_threads = True daemon_threads = True
allow_reuse_address = True
def __init__(self, server_address, RequestHandlerClass, bind_and_activate = True): def __init__(self, server_address, RequestHandlerClass, bind_and_activate = True, want_dual = False):
socketserver.BaseServer.__init__(self, server_address, RequestHandlerClass) socketserver.BaseServer.__init__(self, server_address, RequestHandlerClass)
self.__shutdown_request = False self.__shutdown_request = False
self.r_service, self.w_service = socket.socketpair() self.r_service, self.w_service = socket.socketpair()
@ -47,24 +46,27 @@ class KeyServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
else: else:
self._ServerSelector = selectors.SelectSelector self._ServerSelector = selectors.SelectSelector
try:
ip_ver = ipaddress.ip_address(server_address[0])
except ValueError as e:
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}%s. Exiting...{end}" %str(e))
if ip_ver.version == 4:
self.address_family = socket.AF_INET
elif ip_ver.version == 6:
self.address_family = socket.AF_INET6
self.socket = socket.socket(self.address_family, self.socket_type)
if bind_and_activate: if bind_and_activate:
try: try:
self.server_bind() self.multisock = MultipleListener(server_address, want_dual = want_dual)
self.server_activate() except Exception as e:
except: if want_dual and str(e) == "dualstack_ipv6 not supported on this platform":
self.server_close() try:
raise pretty_printer(log_obj = loggersrv.warning,
put_text = "{reverse}{yellow}{bold}%s. Creating not dualstack sockets...{end}" %str(e))
self.multisock = MultipleListener(server_address, want_dual = False)
except Exception as e:
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}%s. Exiting...{end}" %str(e))
else:
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}%s. Exiting...{end}" %str(e))
if self.multisock.cant_dual:
delim = ('' if len(self.multisock.cant_dual) == 1 else ', ')
pretty_printer(log_obj = loggersrv.warning,
put_text = "{reverse}{yellow}{bold}IPv4 [%s] can't be dualstack{end}" %delim.join(self.multisock.cant_dual))
def pykms_serve(self): def pykms_serve(self):
""" Mixing of socketserver serve_forever() and handle_request() functions, """ Mixing of socketserver serve_forever() and handle_request() functions,
@ -74,7 +76,7 @@ class KeyServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
""" """
# Support people who used socket.settimeout() to escape # Support people who used socket.settimeout() to escape
# pykms_serve() before self.timeout was available. # pykms_serve() before self.timeout was available.
timeout = self.socket.gettimeout() timeout = self.multisock.gettimeout()
if timeout is None: if timeout is None:
timeout = self.timeout timeout = self.timeout
elif self.timeout is not None: elif self.timeout is not None:
@ -85,7 +87,7 @@ class KeyServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
try: try:
# Wait until a request arrives or the timeout expires. # Wait until a request arrives or the timeout expires.
with self._ServerSelector() as selector: with self._ServerSelector() as selector:
selector.register(fileobj = self, events = selectors.EVENT_READ) self.multisock.register(selector)
# self-pipe trick. # self-pipe trick.
selector.register(fileobj = self.r_service.fileno(), events = selectors.EVENT_READ) selector.register(fileobj = self.r_service.fileno(), events = selectors.EVENT_READ)
@ -101,7 +103,9 @@ class KeyServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
return self.handle_timeout() return self.handle_timeout()
else: else:
for key, mask in ready: for key, mask in ready:
if key.fileobj is self: if key.fileobj in self.multisock.filenos():
self.socket = self.multisock.sockmap[key.fileobj]
self.server_address = self.socket.getsockname()
self._handle_request_noblock() self._handle_request_noblock()
elif key.fileobj is self.r_service.fileno(): elif key.fileobj is self.r_service.fileno():
# only to clean buffer. # only to clean buffer.
@ -113,6 +117,9 @@ class KeyServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
def shutdown(self): def shutdown(self):
self.__shutdown_request = True self.__shutdown_request = True
def server_close(self):
self.multisock.close()
def handle_timeout(self): def handle_timeout(self):
pretty_printer(log_obj = loggersrv.error, to_exit = True, pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}Server connection timed out. Exiting...{end}") put_text = "{reverse}{red}{bold}Server connection timed out. Exiting...{end}")
@ -189,10 +196,10 @@ for server OSes and Office >=5', 'def' : None, 'des' : "clientcount"},
'renewal' : {'help' : 'Use this option to specify the renewal interval (in minutes). Default is \"10080\" minutes (7 days).', 'renewal' : {'help' : 'Use this option to specify the renewal interval (in minutes). Default is \"10080\" minutes (7 days).',
'def' : 1440 * 7, 'des' : "renewal"}, 'def' : 1440 * 7, 'des' : "renewal"},
'sql' : {'help' : 'Use this option to store request information from unique clients in an SQLite database. Deactivated by default. \ 'sql' : {'help' : 'Use this option to store request information from unique clients in an SQLite database. Deactivated by default. \
If enabled the default .db file is \"pykms_database.db\". You can also provide a specific location.', If enabled the default .db file is \"pykms_database.db\". You can also provide a specific location.', 'def' : False, 'des' : "sqlite"},
'def' : False, 'des' : "sqlite"},
'hwid' : {'help' : 'Use this option to specify a HWID. The HWID must be an 16-character string of hex characters. \ 'hwid' : {'help' : 'Use this option 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.', 'def' : "364F463A8863D35F", 'des' : "hwid"}, The default is \"364F463A8863D35F\" or type \"RANDOM\" to auto generate the HWID.',
'def' : "364F463A8863D35F", 'des' : "hwid"},
'time0' : {'help' : 'Maximum inactivity time (in seconds) after which the connection with the client is closed. If \"None\" (default) serve forever.', 'time0' : {'help' : 'Maximum inactivity time (in seconds) after which the connection with the client is closed. If \"None\" (default) serve forever.',
'def' : None, 'des' : "timeoutidle"}, 'def' : None, 'des' : "timeoutidle"},
'asyncmsg' : {'help' : 'Prints pretty / logging messages asynchronously. Deactivated by default.', 'asyncmsg' : {'help' : 'Prints pretty / logging messages asynchronously. Deactivated by default.',
@ -204,6 +211,11 @@ Type \"STDOUT\" to view log info on stdout. Type \"FILESTDOUT\" to combine previ
Use \"STDOUTOFF\" to disable stdout messages. Use \"FILEOFF\" if you not want to create logfile.', Use \"STDOUTOFF\" to disable stdout messages. Use \"FILEOFF\" if you not want to create logfile.',
'def' : os.path.join('.', 'pykms_logserver.log'), 'des' : "logfile"}, 'def' : os.path.join('.', 'pykms_logserver.log'), 'des' : "logfile"},
'lsize' : {'help' : 'Use this flag to set a maximum size (in MB) to the output log file. Deactivated by default.', 'def' : 0, 'des': "logsize"}, 'lsize' : {'help' : 'Use this flag to set a maximum size (in MB) to the output log file. Deactivated by default.', 'def' : 0, 'des': "logsize"},
'listen' : {'help' : 'Adds multiple listening address / port couples.', 'des': "listen"},
'backlog' : {'help' : 'Specifies the maximum length of the queue of pending connections. Default is \"5\".', 'def' : 5, 'des': "backlog"},
'reuse' : {'help' : 'Allows binding / listening to the same address and port. Activated by default.', 'def' : True, 'des': "reuse"},
'dual' : {'help' : 'Allows binding / listening to an IPv6 address also accepting connections via IPv4. Deactivated by default.',
'def' : False, 'des': "dual"}
} }
def server_options(): def server_options():
@ -237,6 +249,7 @@ def server_options():
server_parser.add_argument("-h", "--help", action = "help", help = "show this help message and exit") server_parser.add_argument("-h", "--help", action = "help", help = "show this help message and exit")
## Daemon (Etrigan) parsing.
daemon_parser = KmsParser(description = "daemon options inherited from Etrigan", add_help = False) daemon_parser = KmsParser(description = "daemon options inherited from Etrigan", add_help = False)
daemon_subparser = daemon_parser.add_subparsers(dest = "mode") daemon_subparser = daemon_parser.add_subparsers(dest = "mode")
@ -245,57 +258,114 @@ def server_options():
help = "Enable py-kms GUI usage.") help = "Enable py-kms GUI usage.")
etrigan_parser = Etrigan_parser(parser = etrigan_parser) etrigan_parser = Etrigan_parser(parser = etrigan_parser)
## Connection parsing.
connection_parser = KmsParser(description = "connect options", add_help = False)
connection_subparser = connection_parser.add_subparsers(dest = "mode")
connect_parser = connection_subparser.add_parser("connect", add_help = False)
connect_parser.add_argument("-n", "--listen", action = "append", dest = srv_options['listen']['des'], default = [],
help = srv_options['listen']['help'], type = str)
connect_parser.add_argument("-b", "--backlog", action = "append", dest = srv_options['backlog']['des'], default = [],
help = srv_options['backlog']['help'], type = int)
connect_parser.add_argument("-u", "--no-reuse", action = "append_const", dest = srv_options['reuse']['des'], const = False, default = [],
help = srv_options['reuse']['help'])
connect_parser.add_argument("-d", "--dual", action = "store_true", dest = srv_options['dual']['des'], default = srv_options['dual']['def'],
help = srv_options['reuse']['help'])
try: try:
userarg = sys.argv[1:] userarg = sys.argv[1:]
# Run help. # Run help.
if any(arg in ["-h", "--help"] for arg in userarg): if any(arg in ["-h", "--help"] for arg in userarg):
KmsParserHelp().printer(parsers = [server_parser, daemon_parser, etrigan_parser]) KmsParserHelp().printer(parsers = [server_parser, (daemon_parser, etrigan_parser),
(connection_parser, connect_parser)])
# Get stored arguments. # Get stored arguments.
pykmssrv_zeroarg, pykmssrv_onearg = kms_parser_get(server_parser) pykmssrv_zeroarg, pykmssrv_onearg = kms_parser_get(server_parser)
etrigan_zeroarg, etrigan_onearg = kms_parser_get(etrigan_parser) etrigan_zeroarg, etrigan_onearg = kms_parser_get(etrigan_parser)
pykmssrv_zeroarg += ['etrigan'] # add subparser connect_zeroarg, connect_onearg = kms_parser_get(connect_parser)
subpars = ['etrigan', 'connect']
pykmssrv_zeroarg += subpars # add subparsers
# Set defaults for config. exclude_kms = ['-F', '--logfile']
exclude_dup = ['-n', '--listen', '-b', '--backlog', '-u', '--no-reuse']
# Set defaults for server dict config.
# example case: # example case:
# python3 pykms_Server.py # python3 pykms_Server.py
srv_config.update(vars(server_parser.parse_args([]))) srv_config.update(vars(server_parser.parse_args([])))
try: if all(pars in userarg for pars in subpars):
# Eventually set daemon options for dict server config. ## Set `daemon options` and `connect options` for server dict config.
pos = sys.argv[1:].index('etrigan') pos_etr = userarg.index('etrigan')
# example cases: pos_con = userarg.index('connect')
# python3 pykms_Server.py etrigan start pos = min(pos_etr, pos_con)
# python3 pykms_Server.py etrigan start --daemon_optionals
# python3 pykms_Server.py 1.2.3.4 etrigan start
# python3 pykms_Server.py 1.2.3.4 etrigan start --daemon_optionals
# python3 pykms_Server.py 1.2.3.4 1234 etrigan start
# python3 pykms_Server.py 1.2.3.4 1234 etrigan start --daemon_optionals
# python3 pykms_Server.py --pykms_optionals etrigan start
# python3 pykms_Server.py --pykms_optionals etrigan start --daemon_optionals
# python3 pykms_Server.py 1.2.3.4 --pykms_optionals etrigan start
# python3 pykms_Server.py 1.2.3.4 --pykms_optionals etrigan start --daemon_optionals
# python3 pykms_Server.py 1.2.3.4 1234 --pykms_optionals etrigan start
# python3 pykms_Server.py 1.2.3.4 1234 --pykms_optionals etrigan start --daemon_optionals
kms_parser_check_optionals(userarg[0:pos], pykmssrv_zeroarg, pykmssrv_onearg, exclude_opt_len = ['-F', '--logfile']) kms_parser_check_optionals(userarg[0 : pos], pykmssrv_zeroarg, pykmssrv_onearg, exclude_opt_len = exclude_kms)
kms_parser_check_positionals(srv_config, server_parser.parse_args, arguments = userarg[0 : pos], force_parse = True) kms_parser_check_positionals(srv_config, server_parser.parse_args, arguments = userarg[0 : pos], force_parse = True)
kms_parser_check_optionals(userarg[pos:], etrigan_zeroarg, etrigan_onearg, msg = 'optional etrigan')
kms_parser_check_positionals(srv_config, daemon_parser.parse_args, arguments = userarg[pos:], msg = 'positional etrigan')
except ValueError: if pos == pos_etr:
# example case:
# python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals] etrigan daemon_positional [--daemon_optionals] \
# connect [--connect_optionals]
kms_parser_check_optionals(userarg[pos : pos_con], etrigan_zeroarg, etrigan_onearg,
msg = 'optional etrigan')
kms_parser_check_positionals(srv_config, daemon_parser.parse_args, arguments = userarg[pos : pos_con],
msg = 'positional etrigan')
kms_parser_check_optionals(userarg[pos_con:], connect_zeroarg, connect_onearg,
msg = 'optional connect', exclude_opt_dup = exclude_dup)
kms_parser_check_positionals(srv_config, connection_parser.parse_args, arguments = userarg[pos_con:],
msg = 'positional connect')
elif pos == pos_con:
# example case:
# python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals] connect [--connect_optionals] etrigan \
# daemon_positional [--daemon_optionals]
kms_parser_check_optionals(userarg[pos : pos_etr], connect_zeroarg, connect_onearg,
msg = 'optional connect', exclude_opt_dup = exclude_dup)
kms_parser_check_positionals(srv_config, connection_parser.parse_args, arguments = userarg[pos : pos_etr],
msg = 'positional connect')
kms_parser_check_optionals(userarg[pos_etr:], etrigan_zeroarg, etrigan_onearg,
msg = 'optional etrigan')
kms_parser_check_positionals(srv_config, daemon_parser.parse_args, arguments = userarg[pos_etr:],
msg = 'positional etrigan')
srv_config['mode'] = 'etrigan+connect'
elif any(pars in userarg for pars in subpars):
if 'etrigan' in userarg:
pos = userarg.index('etrigan')
elif 'connect' in userarg:
pos = userarg.index('connect')
kms_parser_check_optionals(userarg[0 : pos], pykmssrv_zeroarg, pykmssrv_onearg, exclude_opt_len = exclude_kms)
kms_parser_check_positionals(srv_config, server_parser.parse_args, arguments = userarg[0 : pos], force_parse = True)
if 'etrigan' in userarg:
## Set daemon options for server dict config.
# example case:
# python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals] etrigan daemon_positional [--daemon_optionals]
kms_parser_check_optionals(userarg[pos:], etrigan_zeroarg, etrigan_onearg,
msg = 'optional etrigan')
kms_parser_check_positionals(srv_config, daemon_parser.parse_args, arguments = userarg[pos:],
msg = 'positional etrigan')
elif 'connect' in userarg:
## Set connect options for server dict config.
# example case:
# python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals] connect [--connect_optionals]
kms_parser_check_optionals(userarg[pos:], connect_zeroarg, connect_onearg,
msg = 'optional connect', exclude_opt_dup = exclude_dup)
kms_parser_check_positionals(srv_config, connection_parser.parse_args, arguments = userarg[pos:],
msg = 'positional connect')
else:
# Update pykms options for dict server config. # Update pykms options for dict server config.
# example cases: # example case:
# python3 pykms_Server.py 1.2.3.4 # python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals]
# python3 pykms_Server.py 1.2.3.4 --pykms_optionals kms_parser_check_optionals(userarg, pykmssrv_zeroarg, pykmssrv_onearg, exclude_opt_len = exclude_kms)
# python3 pykms_Server.py 1.2.3.4 1234
# python3 pykms_Server.py 1.2.3.4 1234 --pykms_optionals
# python3 pykms_Server.py --pykms_optionals
kms_parser_check_optionals(userarg, pykmssrv_zeroarg, pykmssrv_onearg, exclude_opt_len = ['-F', '--logfile'])
kms_parser_check_positionals(srv_config, server_parser.parse_args) kms_parser_check_positionals(srv_config, server_parser.parse_args)
kms_parser_check_connect(srv_config, srv_options, userarg, connect_zeroarg, connect_onearg)
except KmsParserException as e: except KmsParserException as e:
pretty_printer(put_text = "{reverse}{red}{bold}%s. Exiting...{end}" %str(e), to_exit = True) pretty_printer(put_text = "{reverse}{red}{bold}%s. Exiting...{end}" %str(e), to_exit = True)
@ -423,17 +493,48 @@ def server_check():
pretty_printer(log_obj = loggersrv.error, to_exit = True, pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}argument `%s`: invalid with: '%s'. Exiting...{end}" %(opt, srv_config[dest])) put_text = "{reverse}{red}{bold}argument `%s`: invalid with: '%s'. Exiting...{end}" %(opt, srv_config[dest]))
def server_create(): # Check further addresses / ports.
if 'listen' in srv_config:
addresses = []
for elem in srv_config['listen']:
try: try:
server = KeyServer((srv_config['ip'], srv_config['port']), kmsServerHandler) addr, port = elem.split(',')
except (socket.gaierror, socket.error) as e: except ValueError:
pretty_printer(log_obj = loggersrv.error, to_exit = True, pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}Connection failed '%s:%d': %s. Exiting...{end}" %(srv_config['ip'], put_text = "{reverse}{red}{bold}argument `-n/--listen`: %s not well defined. Exiting...{end}" %elem)
srv_config['port'], try:
str(e))) port = int(port)
except ValueError:
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}argument `-n/--listen`: port number '%s' is invalid. Exiting...{end}" %port)
if not (1 <= port <= 65535):
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}argument `-n/--listen`: port number '%s' is invalid. Enter between 1 - 65535. Exiting...{end}" %port)
addresses.append((addr, port))
srv_config['listen'] = addresses
def server_create():
# Create address list.
all_address = [(
srv_config['ip'], srv_config['port'],
(srv_config['backlog_primary'] if 'backlog_primary' in srv_config else srv_options['backlog']['def']),
(srv_config['reuse_primary'] if 'reuse_primary' in srv_config else srv_options['reuse']['def'])
)]
log_address = "TCP server listening at %s on port %d" %(srv_config['ip'], srv_config['port'])
if 'listen' in srv_config:
for l, b, r in zip(srv_config['listen'], srv_config['backlog'], srv_config['reuse']):
all_address.append(l + (b,) + (r,))
log_address += justify("at %s on port %d" %(l[0], l[1]), indent = 56)
server = KeyServer(all_address, kmsServerHandler, want_dual = (srv_config['dual'] if 'dual' in srv_config else srv_options['dual']['def']))
server.timeout = srv_config['timeoutidle'] server.timeout = srv_config['timeoutidle']
loggersrv.info("TCP server listening at %s on port %d." % (srv_config['ip'], srv_config['port']))
loggersrv.info(log_address)
loggersrv.info("HWID: %s" % deco(binascii.b2a_hex(srv_config['hwid']), 'utf-8').upper()) loggersrv.info("HWID: %s" % deco(binascii.b2a_hex(srv_config['hwid']), 'utf-8').upper())
return server return server
def server_terminate(generic_srv, exit_server = False, exit_thread = False): def server_terminate(generic_srv, exit_server = False, exit_thread = False):