#!/usr/bin/env python
# -*- coding: utf-8 -*-
# File: powermolecli.py
#
# Copyright 2021 Vincent Schouten
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
#
"""
Main code for powermolecli.
.. _Google Python Style Guide:
http://google.github.io/styleguide/pyguide.html
"""
import argparse
import logging.config
import coloredlogs
from powermolelib import (StateManager,
Heartbeat,
write_ssh_config_file,
TransferAgent,
Tunnel,
ForInstructor,
TorInstructor,
PlainInstructor,
BootstrapAgent)
from powermolecli.lib.helpers import setup_link, parse_config_file, show_menu
from powermolecli.lib.logging import LOGGER_BASENAME
from powermolecli.powermolecliexceptions import SetupFailed
__author__ = '''Vincent Schouten <powermole@protonmail.com>'''
__docformat__ = '''google'''
__date__ = '''12-05-2020'''
__copyright__ = '''Copyright 2021, Vincent Schouten'''
__credits__ = ["Vincent Schouten"]
__license__ = '''MIT'''
__maintainer__ = '''Vincent Schouten'''
__email__ = '''<powermole@protonmail.com>'''
__status__ = '''Development''' # "Prototype", "Development", "Production".
# This is the main prefix used for logging
LOGGER = logging.getLogger(f'{LOGGER_BASENAME}') # non-class objects like functions will consult this object
# Constants, distinct ports
LOCAL_PATH_SSH_CFG = '/tmp/ssh_cfg_minitor' # path to SSH config file (generated by write_ssh_config_file())
LOCAL_PORT_AGENT = 33191 # local (forwarded) used by powermole to send instructions to Agent (all modes)
LOCAL_PORT_PROXY = 8080 # local port used to forward web traffic which exits destination host (only in TOR mode)
LOCAL_PORT_HEARTBEAT = 33193 # local port used by the heartbeat mechanism to communicate with agent (all modes)
LOCAL_PORT_TRANSFER = 33194 # local port used by powermole to upload files to destination host (only in TRANSFER mode)
LOCAL_PORT_COMMAND = 33195 # local port used by powermole to send linux commands to agent (only in COMMAND mode)
REMOTE_PORT_AGENT = 44191 # port on destination host for Agent to listen to incoming instructions (all modes)
REMOTE_PORT_PROXY = 44192 # port on destination host for Agent to receive SOCKS proxified connections
REMOTE_PORT_HEARTBEAT = 44193 # port on destination host for Agent to respond to incoming heartbeats
REMOTE_PORT_TRANSFER = 44194 # port on destination host for Agent to receive raw file data
REMOTE_PORT_COMMAND = 44195 # port on destination host for Agent to interpret Linux commands
DEST_HOST_DEPLOY_PATH = '/tmp/' # path on last host where the Agent will be transferred to
DEBUG = False # set True to capture and show the output of the child (SSH) - highly experimental
HEARTBEAT_INTERVAL = 10 # specify how often (in seconds) the state of the tunnel must be checked
# Constant, grouped ports
GROUP_PORTS = {"local_port_agent": LOCAL_PORT_AGENT,
"local_port_proxy": LOCAL_PORT_PROXY,
"local_port_heartbeat": LOCAL_PORT_HEARTBEAT,
"local_port_transfer": LOCAL_PORT_TRANSFER,
"local_port_command": LOCAL_PORT_COMMAND,
"remote_port_agent": REMOTE_PORT_AGENT,
"remote_port_proxy": REMOTE_PORT_PROXY,
"remote_port_heartbeat": REMOTE_PORT_HEARTBEAT,
"remote_port_transfer": REMOTE_PORT_TRANSFER,
"remote_port_command": REMOTE_PORT_COMMAND}
[docs]def get_arguments():
"""
Gets us the cli arguments.
Returns the args as parsed from the argsparser.
"""
# https://docs.python.org/3/library/argparse.html
parser = argparse.ArgumentParser(
description='''powermole(cli) - anonymizing internet traffic using private hosts''')
parser.add_argument('--config-file',
'-c',
help='The location of the config file',
dest='config_file',
action='store',
default='')
parser.add_argument('--log-level',
'-L',
help='Provide the log level. Defaults to info.',
dest='log_level',
action='store',
default='info',
choices=['debug',
'info',
'warning',
'error',
'critical'])
args = parser.parse_args()
return args
[docs]def main():
"""
Main method.
This method holds what you want to execute when
the script is run on command line.
coloredlogs.install(level=None, **kw): Enables colored terminal output for Python’s logging module.
https://coloredlogs.readthedocs.io/en/latest/api.html#coloredlogs.install
"""
args = get_arguments()
log_format = '%(asctime)s %(name)s[%(process)d] %(levelname)s %(message)s' # The string '(hostname)s' is omitted
field_styles = {'asctime': {'color': 'green'},
'name': {'color': 'blue'},
'levelname': {'bold': True, 'color': 234}} # Default colour is 'black'
coloredlogs.install(level=args.log_level.upper(), field_styles=field_styles, fmt=log_format)
config = parse_config_file(args.config_file)
if not config:
raise SystemExit(1)
try:
with StateManager() as state:
write_ssh_config_file(LOCAL_PATH_SSH_CFG, config.gateways, config.destination)
transferagent = TransferAgent(LOCAL_PATH_SSH_CFG, config.all_host_addr, DEST_HOST_DEPLOY_PATH)
if config.mode == 'FOR':
tunnel = Tunnel(LOCAL_PATH_SSH_CFG, config.mode, config.all_host_addr, GROUP_PORTS,
config.forwarders_string)
instructor = ForInstructor(GROUP_PORTS)
message = f'connections on local ports {config.forwarders_ports} will be forwarded'
elif config.mode == 'TOR':
tunnel = Tunnel(LOCAL_PATH_SSH_CFG, config.mode, config.all_host_addr, GROUP_PORTS)
instructor = TorInstructor(GROUP_PORTS, config.destination["host_ip"])
message = f'local port {GROUP_PORTS["local_port_proxy"]} will be listening for SOCKS encapsulated ' \
'web traffic'
elif config.mode == 'PLAIN':
tunnel = Tunnel(LOCAL_PATH_SSH_CFG, config.mode, config.all_host_addr, GROUP_PORTS)
instructor = PlainInstructor(GROUP_PORTS)
bootstrapagent = BootstrapAgent(tunnel, GROUP_PORTS, DEST_HOST_DEPLOY_PATH)
setup_link(state, transferagent, tunnel, bootstrapagent, instructor, debug=False)
tunnel.periodically_purge_buffer()
with Heartbeat(GROUP_PORTS["local_port_heartbeat"], HEARTBEAT_INTERVAL):
LOGGER.info(message)
try:
input('READY - Press the <<Enter>> key for options...\n')
show_menu(config, instructor)
except EOFError:
LOGGER.debug("EOF caught")
except SetupFailed as msg:
# custom exception is defined in "powermolecliexceptions.py" and can only be raised by setup_link() in
# the helpers module. the exception is raised when an object (eg. TransferAgent, Tunnel, Assistant)
# cannot be started (start()) successfully.
LOGGER.error(msg)
raise SystemExit(1) from None