Created
November 3, 2017 20:36
-
-
Save harlowja/43453ca1fce77b68655cc165e666a220 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import contextlib | |
import errno | |
import itertools | |
import logging | |
import pkg_resources | |
import re | |
import socket | |
import threading | |
import traceback | |
import futurist | |
import munch | |
import six | |
from daddy import channel as c | |
from daddy import exceptions as excp | |
from daddy import message as m | |
LOG = logging.getLogger(__name__) | |
class FileProxy(object): | |
def __init__(self, conn): | |
self._handle = conn.makefile("rw") | |
self._lock = threading.Lock() | |
def close(self): | |
self._handle.close() | |
def write(self, blob): | |
with self._lock: | |
self._handle.write(blob) | |
self._handle.flush() | |
def read_line(self, prompt=None): | |
if prompt: | |
self.write(prompt) | |
return self._handle.readline() | |
def write_lines(self, lines, add_newline=True): | |
with self._lock: | |
self._handle.write("\r\n".join(lines)) | |
if add_newline: | |
self._handle.write("\r\n") | |
self._handle.flush() | |
def write_line(self, line): | |
with self._lock: | |
self._handle.write(line) | |
self._handle.write("\r\n") | |
self._handle.flush() | |
class Client(threading.Thread): | |
what = 'daddy' | |
welcome_tpl = ("You have connected to the %s v%s" | |
" backdoor micro-telnet server.") | |
prompt_tpl = "%s Telnet Server> " | |
close_tpl = "Session to %s disconnected." | |
result_wait = 1 | |
def __init__(self, bot, socket_pair, ts_counter): | |
super(Client, self).__init__() | |
self.bot = bot | |
self.socket_pair = socket_pair | |
self.daemon = True | |
self.ts_counter = ts_counter | |
def run(self): | |
conn, addr = self.socket_pair | |
connected_to = "%s:%s" % addr | |
f = FileProxy(conn) | |
try: | |
with contextlib.closing(f): | |
with contextlib.closing(conn): | |
self._run(f, connected_to) | |
except (IOError, socket.error) as e: | |
if e.errno in (errno.ECONNRESET, errno.EPIPE): | |
pass | |
else: | |
LOG.warning("Received error closing connection to %s.", | |
connected_to, exc_info=True) | |
LOG.debug(self.close_tpl, connected_to) | |
def _run(self, f, connected_to): | |
me = pkg_resources.get_distribution(self.what) | |
welcome = self.welcome_tpl % (self.what, me.version) | |
prompt = self.prompt_tpl % (self.what.title()) | |
def replier(text, **kwargs): | |
f.write_line(" " + text) | |
def reply_maker(message, wants_attachments=False): | |
if wants_attachments: | |
raise NotImplementedError | |
else: | |
return replier | |
f.write_line(welcome) | |
while True: | |
try: | |
line = f.read_line(prompt=prompt) | |
except EOFError: | |
break | |
except (IOError, socket.error) as e: | |
if e.errno in (errno.ECONNRESET, errno.EPIPE): | |
break | |
else: | |
LOG.warning("Received error reading line from %s.", | |
connected_to, exc_info=True) | |
else: | |
line = line.strip() | |
thread_ts = None | |
# Simple threading... | |
t_m = re.match(r"^@(\d+)\s+(.*)$", line) | |
if t_m: | |
thread_ts = t_m.group(1) | |
line = t_m.group(2) | |
ts = str(six.next(self.ts_counter)) | |
m_headers = { | |
m.VALIDATED_HEADER: True, | |
m.TO_ME_HEADER: True, | |
m.CHECK_AUTH_HEADER: False, | |
} | |
m_body = munch.Munch({ | |
'text': line, | |
'ts': ts, | |
'thread_ts': thread_ts, | |
'text_no_links': line, | |
'username': connected_to, | |
'user_id': connected_to, | |
}) | |
if thread_ts is not None: | |
fut = self.bot.submit_message( | |
m.Message("repl/message", m_headers, | |
m_body, reply_maker=reply_maker), | |
c.FOLLOWUP) | |
else: | |
fut = self.bot.submit_message( | |
m.Message("repl/message", m_headers, | |
m_body, reply_maker=reply_maker), | |
c.TARGETED) | |
lines = [ | |
"Submitted '%s'" % (line), | |
"Thread = '%s'" % ts, | |
] | |
if thread_ts is not None: | |
lines.append("Thread ts = '%s'" % thread_ts) | |
lines.append("Please wait for result...") | |
f.write_lines(lines) | |
f_waiting = True | |
while f_waiting: | |
try: | |
fut.result(timeout=self.result_wait) | |
f_waiting = False | |
except futurist.TimeoutError: | |
pass | |
except Exception: | |
f_waiting = False | |
f.write_line("Done!") | |
try: | |
res = fut.result() | |
except Exception as e: | |
if isinstance(e, excp.NoHandlerFound) and e.suggestion: | |
f.write_lines([ | |
"Finished:", | |
" Perhaps you meant '%s'?" % e.suggestion, | |
]) | |
else: | |
lines = [ | |
"Failed:", | |
] | |
buf = six.StringIO() | |
traceback.print_exc(file=buf) | |
for line in buf.getvalue().splitlines(): | |
if not line: | |
continue | |
lines.append(" " + line) | |
f.write_lines(lines) | |
else: | |
f.write_lines([ | |
"Finished:", | |
" Result = %s" % res, | |
]) | |
class Server(threading.Thread): | |
def __init__(self, bot, port): | |
super(Server, self).__init__() | |
self.bot = bot | |
self.daemon = True | |
self.dead = threading.Event() | |
self.port = port | |
def run(self): | |
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | |
# This ensures the message ts (from slack terminology) is always | |
# increasing, no matter how many clients and ... | |
ts_counter = itertools.count(0) | |
with contextlib.closing(sock): | |
sock.bind(('localhost', self.port)) | |
sock.listen(2) | |
print("Backdoor server listening on %s:%s" % sock.getsockname()) | |
self.port = sock.getsockname()[1] | |
while True: | |
try: | |
socket_pair = sock.accept() | |
except socket.timeout: | |
pass | |
else: | |
conn_client = Client(self.bot, socket_pair, | |
ts_counter) | |
conn_client.start() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment