diff --git a/README.md b/README.md index e559c15..50ad57a 100644 --- a/README.md +++ b/README.md @@ -54,3 +54,12 @@ That said, one should not rely on any software when the stakes are too high. ![](dummy.png) + + +# Limitations + Road map + +This project will forever follow the KISS principle, but these two three will be addressed. + +* Multi-byte character support (full utf-8 support) +* Tor bridge support +* Support non-anonymous hidden services. Mainly useful for certain development needs diff --git a/setup.py b/setup.py index 827fe6a..d51fb8c 100644 --- a/setup.py +++ b/setup.py @@ -16,6 +16,7 @@ setup(name='youandme', scripts=['src/yam/yam.py'], author_email='beardog@mailbox.org', url='http://github.com/beardog108/youandme', + python_requires='>=3.7', install_requires=[ 'stem', 'PySocks' diff --git a/src/yam/yam.py b/src/yam/yam.py index c144e4a..e21214f 100644 --- a/src/yam/yam.py +++ b/src/yam/yam.py @@ -6,6 +6,7 @@ from threading import Thread import tempfile import argparse from string import printable +from base64 import b85decode, b85encode import socks from stem.control import Controller @@ -15,6 +16,7 @@ try: except ModuleNotFoundError: pass +from youandme.commands import terminator from youandme.server import server from youandme.client import client @@ -22,31 +24,37 @@ from youandme.client import client class Connection: connected = True + class _Address: address = "" - def _get_open_port(): - # taken from (but modified) https://stackoverflow.com/a/2838309 by https://stackoverflow.com/users/133374/albert ccy-by-sa-3 https://creativecommons.org/licenses/by-sa/3.0/ - # changes from source: import moved to top of file, bind specifically to localhost + # taken from (but modified) stackoverflow.com/a/2838309 + # by stackoverflow.com/users/133374/albert + # ccy-by-sa-3 creativecommons.org/licenses/by-sa/3.0/ + # changes from source: import moved to top of file, bind to localhost # vulnerable to race condition s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.bind(("127.0.0.1",0)) + s.bind(("127.0.0.1", 0)) s.listen(1) port = s.getsockname()[1] s.close() return port -def connector(host, send_data, recv_data, address="", control_port=1337, socks_port=1338): + +def connector(host, send_data, recv_data, + address="", control_port=1337, socks_port=1338): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) result = sock.connect_ex(('127.0.0.1', socks_port)) + sock.close() if result != 0: try: launch_tor(control_port=control_port, socks_port=socks_port) except OSError: with tempfile.TemporaryDirectory() as tmpdirname: - launch_tor(control_port=control_port, socks_port=socks_port, data_dir=tmpdirname) + launch_tor(control_port=control_port, + socks_port=socks_port, data_dir=tmpdirname) if host: with Controller.from_port(port=control_port) as controller: controller.authenticate() @@ -62,7 +70,8 @@ def connector(host, send_data, recv_data, address="", control_port=1337, socks_p ) _Address.address = serv.service_id conn, addr = s.accept() - server(0.01, controller, conn, send_data, recv_data, Connection) + server(0.01, controller, conn, send_data, recv_data, + Connection) else: if not address.endswith('.onion'): address += '.onion' @@ -71,8 +80,10 @@ def connector(host, send_data, recv_data, address="", control_port=1337, socks_p except socks.GeneralProxyError: Connection.connected = False + def chat(mode, send_data, recv_data, alpha): - print("A notice will be shown when connection is established, but messages may be typed now.") + print("A notice will be shown when connection is established," + + "but messages may be typed now.") display_buffer = [] if mode == 'host': @@ -83,27 +94,16 @@ def chat(mode, send_data, recv_data, alpha): print(_Address.address) def display_new(): - while True: - try: - char = chr(recv_data.pop(0)) - display_buffer.append(char) - if char == "\n" or char == "\r\n" or len(display_buffer) > 100: - while len(display_buffer) != 0: - char = display_buffer.pop(0) - if alpha and char not in printable: - continue - print("\033[1;33m" + char + "\033[0m", end="") - - except IndexError: - pass - sleep(0.1) + for message in get_messages Thread(target=display_new, daemon=True).start() + def make_message(): while True: - new = input("\033[0m").encode('utf-8') + new = input("\033[0m").encode('utf-8') # nosec for b in new: send_data.append(b) send_data.append(ord(b"\n")) + Thread(target=make_message, daemon=True).start() while True: try: @@ -113,15 +113,25 @@ def chat(mode, send_data, recv_data, alpha): sleep(1) except KeyboardInterrupt: break + + PORT_MESSAGE = "Specify any free ports above 1023" if __name__ == "__main__": - parser = argparse.ArgumentParser(description='End-to-end encrypted instant messaging with metadata privacy and perfect forward secrecy') + parser = argparse.ArgumentParser( + description='End-to-end encrypted messaging with metadata privacy') parser.add_argument('connection', choices=['host', 'conn']) - parser.add_argument('--socks-port', help='Socks proxy port (will use random if not given)', default=0, type=int) - parser.add_argument('--control-port', help='Tor control port (will use random if not given)', default=0, type=int) - parser.add_argument('--alphanum-only', help='Only allow english alpha-numeric characters and spaces/tabs/new lines', default=False, type=bool) - parser.add_argument('--address', help='Address to connect to. No port.', default='') + parser.add_argument('--socks-port', + help='Socks proxy port (will use random if not given)', + default=0, type=int) + parser.add_argument('--control-port', + help='Tor control port (will use random if not given)', + default=0, type=int) + parser.add_argument('--alphanum-only', + help='Only stdout en-us typical characters', + default=False, type=bool) + parser.add_argument('--address', + help='Address to connect to. No port.', default='') args = parser.parse_args() if args.socks_port == 0: args.socks_port = _get_open_port() @@ -130,16 +140,17 @@ if __name__ == "__main__": recv_data = bytearray() if args.connection == 'conn': if not args.address: - print("Must specify address if connecting (--address)", file=sys.stderr) + print("Must specify address if connecting (--address)", + file=sys.stderr) sys.exit(3) Thread(target=connector, args=[False, send_data, recv_data], kwargs={'address': args.address, 'socks_port': args.socks_port, - 'control_port': args.control_port}, daemon=True).start() + 'control_port': args.control_port}, daemon=True).start() chat('conn', send_data, recv_data, args.alphanum_only) else: Thread(target=connector, args=[True, send_data, recv_data], kwargs={'socks_port': args.socks_port, - 'control_port': args.control_port}, daemon=True).start() + 'control_port': args.control_port}, daemon=True).start() chat('host', send_data, recv_data, args.alphanum_only) \ No newline at end of file diff --git a/src/youandme/commands.py b/src/youandme/commands.py index af458da..962a0b9 100644 --- a/src/youandme/commands.py +++ b/src/youandme/commands.py @@ -1,2 +1,3 @@ garbage_character = b'\x00' +terminator = 1 WELCOME_MESSAGE = "Connection established. Dance like no one is watching :)\n" \ No newline at end of file diff --git a/src/youandme/stream.py b/src/youandme/stream.py new file mode 100644 index 0000000..55594b3 --- /dev/null +++ b/src/youandme/stream.py @@ -0,0 +1,19 @@ +from base64 import b85encode, b85decode +from time import sleep + +from youandme.commands import terminator + +_b85alphabet = (b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + b"abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~") + + +def decoded_recv_stream(raw_stream: bytearray, delay_seconds: int, max_buffer_size=) -> bytes: + while True: + for byte in raw_stream: + if byte == terminator: + yield b85decode(raw_stream[:len(raw_stream) - 1]) + raw_stream.clear() + continue + if byte not in _b85alphabet: + raise ValueError('Not valid base85 encoding') + sleep(delay_seconds) diff --git a/tests/test_stream.py b/tests/test_stream.py new file mode 100644 index 0000000..3a9b09d --- /dev/null +++ b/tests/test_stream.py @@ -0,0 +1,34 @@ +import unittest +import socket +from threading import Thread +import sys +import time +from base64 import b85decode, b85encode +from stem.control import Controller +from youandme import stream +from youandme.commands import terminator + + +def add_encoded_to_bytes_array_valid(message, array: bytearray): + for m in b85encode(message.encode('utf-8')): + array.append(m) + array.append(terminator) + +class TestStream(unittest.TestCase): + + def test_stream_get(self): + send_data = bytearray() + recv_data = bytearray() + + data = "hello world" + #stream.decoded_recv_stream(recv_data, data) + add_encoded_to_bytes_array_valid(data, recv_data) + + c = 0 + for message in stream.decoded_recv_stream(recv_data, 1): + self.assertEqual(message.decode('utf-8'), data) + break + + + +unittest.main() \ No newline at end of file