You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

128 lines
4.2 KiB

import argparse
import configparser
import json
import socket
import ssl
from datetime import datetime
from os import environ
import paho.mqtt.publish as publish
import socks
import stem.connection
from stem.control import Controller
def main():
parser = argparse.ArgumentParser(description='Broadcast SSH hidden service hostname via MQTT')
parser.add_argument('--config-dir', nargs='?', dest='configPath', default='/etc/torch',
help='configuration directory (default: /etc/torch)')
args = parser.parse_args()
config_path = args.configPath
if "TORCH_CONFIG_DIR" in environ:
config_path = environ.get("TORCH_CONFIG_DIR")
if not config_path.endswith("/"):
config_path = config_path + "/"
print("Using torch configuration path: " + config_path)
config = configparser.ConfigParser()
configuration_file_path = config_path + "torch.conf"
print("Reading configuration file at '%s'" % configuration_file_path)
tor_proxy_host = config['tor'].get('ProxyHost', fallback="")
tor_proxy_port = config['tor'].getint('ProxyPort', fallback=9050)
tor_controller_port = config['tor'].getint('ControllerPort', fallback=9051)
ssh_port = config['ssh'].getint('Port', fallback=22)
mqtt_config = config['mqtt']
mqtt_broker_host = mqtt_config.get('BrokerHost', fallback="localhost")
mqtt_broker_port = mqtt_config.getint('BrokerPort', fallback=1883)
mqtt_broker_using_tor = mqtt_broker_host.endswith(".onion")
mqtt_client_id = mqtt_config.get('ClientID', fallback=socket.gethostname())
mqtt_topic = mqtt_config.get('Topic', fallback="torch/%s/onion_url" % mqtt_client_id)
mqtt_require_certificate = mqtt_config.getboolean(
mqtt_ca_file = mqtt_config.get('CaFile', fallback=None)
mqtt_ca_file = config_path + mqtt_ca_file
mqtt_cert_file = mqtt_config.get('CertFile', fallback=None)
mqtt_cert_file = config_path + mqtt_cert_file
mqtt_key_file = mqtt_config.get('KeyFile', fallback=None)
mqtt_key_file = config_path + mqtt_key_file
mqtt_use_tls = \
mqtt_ca_file is not None and \
mqtt_cert_file is not None and \
mqtt_key_file is not None
print("Connecting to local TOR controller on port %s" % tor_controller_port)
with Controller.from_port(port=tor_controller_port) as controller:
protocol_info = stem.connection.get_protocolinfo(controller)
stem.connection.authenticate_safecookie(controller, protocol_info.cookie_path)
print("Creating TOR Hidden Service...")
service = controller.create_ephemeral_hidden_service(ssh_port, detached=True)
onion_address = "%s.onion" % service.service_id
print("Created Tor Hidden Service for local service on port %s at %s" % (ssh_port, onion_address))
protocol = "mqtt"
tls_args = None
proxy_args = None
cert_required = ssl.CERT_OPTIONAL
if mqtt_require_certificate:
cert_required = ssl.CERT_REQUIRED
if mqtt_broker_using_tor:
cert_required = ssl.CERT_OPTIONAL
proxy_args = {
'proxy_type': socks.SOCKS5,
'proxy_addr': tor_proxy_host,
'proxy_port': tor_proxy_port
if mqtt_use_tls:
protocol = "mqtts"
tls_args = {
'ca_certs': mqtt_ca_file,
'certfile': mqtt_cert_file,
'keyfile': mqtt_key_file,
'cert_reqs': cert_required
print("Publishing to MQTT broker: %s://%s:%s/%s" % (protocol, mqtt_broker_host, mqtt_broker_port, mqtt_topic))
if mqtt_broker_using_tor:
print("--> Using TOR proxy: %s:%s" % (tor_proxy_host, tor_proxy_port))
payload = json.dumps({
'clientId': mqtt_client_id,
'timestamp':"%d-%b-%Y (%H:%M:%S.%f)"),
'onionAddress': onion_address,
'sshPort': ssh_port