mirror of
https://github.com/SystemRage/py-kms.git
synced 2024-11-25 17:55:37 +01:00
Support for multi-address connection
This commit is contained in:
parent
016a4c367f
commit
990cd5e48f
215
py-kms/pykms_Connect.py
Normal file
215
py-kms/pykms_Connect.py
Normal 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
|
@ -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():
|
||||
|
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user