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
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 = ''
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(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):
def subprinter(adj, sub, replace):
print(adj.description + '\n')
for line in self.replace(sub, replace):
print(line)
except:
pass
print('\n')
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(parser_base.epilog + '\n')
parser_base.exit()
@ -363,13 +373,13 @@ def kms_parser_get(parser):
act = vars(parser)['_actions']
for i in range(len(act)):
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)
else:
zeroarg.append(act[i].option_strings)
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:
Don't allow duplicates,
@ -399,12 +409,13 @@ def kms_parser_check_optionals(userarg, zeroarg, onearg, msg = 'optional py-kms
# Check duplicates.
founds = [i for i in userarg if i in allarg]
dup = [item for item in set(founds) if founds.count(item) > 1]
if dup != []:
raise KmsParserException("%s argument `%s` appears several times" %(msg, ', '.join(dup)))
for d in dup:
if d not in exclude_opt_dup:
raise KmsParserException("%s argument `%s` appears several times" %(msg, ', '.join(dup)))
# Check length.
elem = None
for found in founds:
for found in set(founds):
if found not in exclude_opt_len:
pos = userarg.index(found)
try:
@ -433,6 +444,70 @@ def kms_parser_check_positionals(config, parse_method, arguments = [], force_par
else:
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):
for key in dictionary.keys():

View file

@ -14,16 +14,16 @@ import socketserver
import queue as Queue
import selectors
from time import monotonic as time
import ipaddress
import pykms_RpcBind, pykms_RpcRequest
from pykms_RpcBase import rpcBase
from pykms_Dcerpc import MSRPCHeader
from pykms_Misc import check_setup, check_lcid, check_dir
from pykms_Misc import KmsParser, KmsParserException, KmsParserHelp
from pykms_Misc import kms_parser_get, kms_parser_check_optionals, kms_parser_check_positionals
from pykms_Format import enco, deco, pretty_printer
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, justify
from Etrigan import Etrigan, Etrigan_parser, Etrigan_check, Etrigan_job
from pykms_Connect import MultipleListener
srv_version = "py-kms_2020-07-01"
__license__ = "The Unlicense"
@ -35,9 +35,8 @@ srv_config = {}
##---------------------------------------------------------------------------------------------------------------------------------------------------------
class KeyServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
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)
self.__shutdown_request = False
self.r_service, self.w_service = socket.socketpair()
@ -47,24 +46,27 @@ class KeyServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
else:
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:
try:
self.server_bind()
self.server_activate()
except:
self.server_close()
raise
self.multisock = MultipleListener(server_address, want_dual = want_dual)
except Exception as e:
if want_dual and str(e) == "dualstack_ipv6 not supported on this platform":
try:
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):
""" 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
# pykms_serve() before self.timeout was available.
timeout = self.socket.gettimeout()
timeout = self.multisock.gettimeout()
if timeout is None:
timeout = self.timeout
elif self.timeout is not None:
@ -85,7 +87,7 @@ class KeyServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
try:
# Wait until a request arrives or the timeout expires.
with self._ServerSelector() as selector:
selector.register(fileobj = self, events = selectors.EVENT_READ)
self.multisock.register(selector)
# self-pipe trick.
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()
else:
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()
elif key.fileobj is self.r_service.fileno():
# only to clean buffer.
@ -113,6 +117,9 @@ class KeyServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
def shutdown(self):
self.__shutdown_request = True
def server_close(self):
self.multisock.close()
def handle_timeout(self):
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}Server connection timed out. Exiting...{end}")
@ -176,34 +183,39 @@ loggersrv = logging.getLogger('logsrv')
# 'help' string - 'default' value - 'dest' string.
srv_options = {
'ip' : {'help' : 'The IP address (IPv4 or IPv6) to listen on. The default is \"0.0.0.0\" (all interfaces).', 'def' : "0.0.0.0", 'des' : "ip"},
'port' : {'help' : 'The network port to listen on. The default is \"1688\".', 'def' : 1688, 'des' : "port"},
'epid' : {'help' : 'Use this option to manually specify an ePID to use. If no ePID is specified, a random ePID will be auto generated.',
'def' : None, 'des' : "epid"},
'lcid' : {'help' : 'Use this option to manually specify an LCID for use with randomly generated ePIDs. Default is \"1033\" (en-us)',
'def' : 1033, 'des' : "lcid"},
'count' : {'help' : 'Use this option to specify the current client count. A number >=25 is required to enable activation of client OSes; \
'ip' : {'help' : 'The IP address (IPv4 or IPv6) to listen on. The default is \"0.0.0.0\" (all interfaces).', 'def' : "0.0.0.0", 'des' : "ip"},
'port' : {'help' : 'The network port to listen on. The default is \"1688\".', 'def' : 1688, 'des' : "port"},
'epid' : {'help' : 'Use this option to manually specify an ePID to use. If no ePID is specified, a random ePID will be auto generated.',
'def' : None, 'des' : "epid"},
'lcid' : {'help' : 'Use this option to manually specify an LCID for use with randomly generated ePIDs. Default is \"1033\" (en-us)',
'def' : 1033, 'des' : "lcid"},
'count' : {'help' : 'Use this option to specify the current client count. A number >=25 is required to enable activation of client OSes; \
for server OSes and Office >=5', 'def' : None, 'des' : "clientcount"},
'activation' : {'help' : 'Use this option to specify the activation interval (in minutes). Default is \"120\" minutes (2 hours).',
'def' : 120, 'des': "activation"},
'renewal' : {'help' : 'Use this option to specify the renewal interval (in minutes). Default is \"10080\" minutes (7 days).',
'def' : 1440 * 7, 'des' : "renewal"},
'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.',
'def' : False, 'des' : "sqlite"},
'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"},
'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"},
'asyncmsg' : {'help' : 'Prints pretty / logging messages asynchronously. Deactivated by default.',
'def' : False, 'des' : "asyncmsg"},
'llevel' : {'help' : 'Use this option to set a log level. The default is \"ERROR\".', 'def' : "ERROR", 'des' : "loglevel",
'choi' : ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "MININFO"]},
'lfile' : {'help' : 'Use this option to set an output log file. The default is \"pykms_logserver.log\". \
'renewal' : {'help' : 'Use this option to specify the renewal interval (in minutes). Default is \"10080\" minutes (7 days).',
'def' : 1440 * 7, 'des' : "renewal"},
'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.', 'def' : False, 'des' : "sqlite"},
'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"},
'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"},
'asyncmsg' : {'help' : 'Prints pretty / logging messages asynchronously. Deactivated by default.',
'def' : False, 'des' : "asyncmsg"},
'llevel' : {'help' : 'Use this option to set a log level. The default is \"ERROR\".', 'def' : "ERROR", 'des' : "loglevel",
'choi' : ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "MININFO"]},
'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. \
Use \"STDOUTOFF\" to disable stdout messages. Use \"FILEOFF\" if you not want to create 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"},
'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"},
'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():
@ -237,6 +249,7 @@ def server_options():
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_subparser = daemon_parser.add_subparsers(dest = "mode")
@ -245,57 +258,114 @@ def server_options():
help = "Enable py-kms GUI usage.")
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:
userarg = sys.argv[1:]
# Run help.
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.
pykmssrv_zeroarg, pykmssrv_onearg = kms_parser_get(server_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:
# python3 pykms_Server.py
srv_config.update(vars(server_parser.parse_args([])))
try:
# Eventually set daemon options for dict server config.
pos = sys.argv[1:].index('etrigan')
# example cases:
# python3 pykms_Server.py etrigan start
# 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
if all(pars in userarg for pars in subpars):
## Set `daemon options` and `connect options` for server dict config.
pos_etr = userarg.index('etrigan')
pos_con = userarg.index('connect')
pos = min(pos_etr, pos_con)
kms_parser_check_optionals(userarg[0:pos], pykmssrv_zeroarg, pykmssrv_onearg, exclude_opt_len = ['-F', '--logfile'])
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')
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)
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.
# example cases:
# python3 pykms_Server.py 1.2.3.4
# python3 pykms_Server.py 1.2.3.4 --pykms_optionals
# 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'])
# example case:
# python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals]
kms_parser_check_optionals(userarg, pykmssrv_zeroarg, pykmssrv_onearg, exclude_opt_len = exclude_kms)
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:
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,
put_text = "{reverse}{red}{bold}argument `%s`: invalid with: '%s'. Exiting...{end}" %(opt, srv_config[dest]))
# Check further addresses / ports.
if 'listen' in srv_config:
addresses = []
for elem in srv_config['listen']:
try:
addr, port = elem.split(',')
except ValueError:
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}argument `-n/--listen`: %s not well defined. Exiting...{end}" %elem)
try:
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():
try:
server = KeyServer((srv_config['ip'], srv_config['port']), kmsServerHandler)
except (socket.gaierror, socket.error) as e:
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}Connection failed '%s:%d': %s. Exiting...{end}" %(srv_config['ip'],
srv_config['port'],
str(e)))
# 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']
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())
return server
def server_terminate(generic_srv, exit_server = False, exit_thread = False):