2020-04-23 09:23:57 +00:00
#!/usr/bin/env python3
2020-04-16 11:41:49 +00:00
import sys
2020-04-20 08:36:41 +00:00
import socket
2020-04-24 03:18:29 +00:00
from time import sleep
2020-04-20 08:36:41 +00:00
from threading import Thread
2020-04-23 09:23:57 +00:00
import tempfile
import argparse
2020-04-24 03:18:29 +00:00
from string import printable
2020-04-20 08:36:41 +00:00
2020-04-24 03:18:29 +00:00
import socks
2020-04-20 08:36:41 +00:00
from stem . control import Controller
2020-04-16 11:41:49 +00:00
try :
from youandme . tor import launch_tor
except ModuleNotFoundError :
pass
from youandme . server import server
2020-04-20 08:36:41 +00:00
from youandme . client import client
2020-04-24 03:18:29 +00:00
class Connection :
connected = True
2020-04-20 08:36:41 +00:00
class _Address :
address = " "
2020-04-24 03:18:29 +00:00
2020-04-23 09:23:57 +00:00
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
# vulnerable to race condition
s = socket . socket ( socket . AF_INET , socket . SOCK_STREAM )
s . bind ( ( " 127.0.0.1 " , 0 ) )
s . listen ( 1 )
port = s . getsockname ( ) [ 1 ]
s . close ( )
return port
2020-04-20 08:36:41 +00:00
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 ) )
if result != 0 :
2020-04-23 09:23:57 +00:00
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 )
2020-04-20 08:36:41 +00:00
if host :
with Controller . from_port ( port = control_port ) as controller :
controller . authenticate ( )
with socket . socket ( socket . AF_INET , socket . SOCK_STREAM ) as s :
ip = ' 127.0.0.1 '
s . bind ( ( ip , 0 ) )
s . listen ( 1 )
port = s . getsockname ( ) [ 1 ]
serv = controller . create_ephemeral_hidden_service (
{ 1337 : ' 127.0.0.1: ' + str ( port ) } ,
key_content = ' ED25519-V3 ' ,
await_publication = True ,
)
_Address . address = serv . service_id
conn , addr = s . accept ( )
2020-04-24 03:18:29 +00:00
server ( 0.01 , controller , conn , send_data , recv_data , Connection )
2020-04-20 08:36:41 +00:00
else :
if not address . endswith ( ' .onion ' ) :
address + = ' .onion '
2020-04-24 03:18:29 +00:00
try :
client ( 0.01 , address , socks_port , send_data , recv_data , Connection )
except socks . GeneralProxyError :
Connection . connected = False
2020-04-16 11:41:49 +00:00
2020-04-24 03:18:29 +00:00
def chat ( mode , send_data , recv_data , alpha ) :
print ( " A notice will be shown when connection is established, but messages may be typed now. " )
2020-04-20 11:15:19 +00:00
display_buffer = [ ]
2020-04-24 03:18:29 +00:00
2020-04-20 08:36:41 +00:00
if mode == ' host ' :
2020-04-24 03:18:29 +00:00
print ( ' Creating tunnel... ' )
2020-04-20 08:36:41 +00:00
while _Address . address == " " :
sleep ( 0.01 )
2020-04-24 03:18:29 +00:00
print ( ' Tunnel address: ' )
2020-04-20 08:36:41 +00:00
print ( _Address . address )
2020-04-24 03:18:29 +00:00
2020-04-20 08:36:41 +00:00
def display_new ( ) :
while True :
try :
char = chr ( recv_data . pop ( 0 ) )
2020-04-20 11:15:19 +00:00
display_buffer . append ( char )
if char == " \n " or char == " \r \n " or len ( display_buffer ) > 100 :
while len ( display_buffer ) != 0 :
2020-04-24 03:18:29 +00:00
char = display_buffer . pop ( 0 )
if alpha and char not in printable and \
char not in ( ' \n ' , ' \r ' , ' ' , ' \t ' ) :
continue
print ( " \033 [1;33m " + char + " \033 [0m " , end = " " )
2020-04-20 11:15:19 +00:00
2020-04-20 08:36:41 +00:00
except IndexError :
pass
sleep ( 0.1 )
Thread ( target = display_new , daemon = True ) . start ( )
def make_message ( ) :
while True :
2020-04-20 11:15:19 +00:00
new = input ( " \033 [0m " ) . encode ( ' utf-8 ' )
2020-04-20 08:36:41 +00:00
for b in new :
send_data . append ( b )
2020-04-20 11:15:19 +00:00
send_data . append ( ord ( b " \n " ) )
2020-04-20 08:36:41 +00:00
Thread ( target = make_message , daemon = True ) . start ( )
while True :
2020-04-20 11:15:19 +00:00
try :
2020-04-24 03:18:29 +00:00
if not Connection . connected :
print ( " Cease dancing ☹️ " )
2020-04-20 11:15:19 +00:00
break
sleep ( 1 )
except KeyboardInterrupt :
2020-04-23 09:23:57 +00:00
break
PORT_MESSAGE = " Specify any free ports above 1023 "
2020-04-16 11:41:49 +00:00
if __name__ == " __main__ " :
2020-04-24 03:18:29 +00:00
parser = argparse . ArgumentParser ( description = ' End-to-end encrypted instant messaging with metadata privacy and perfect forward secrecy ' )
2020-04-23 09:23:57 +00:00
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 )
2020-04-24 03:18:29 +00:00
parser . add_argument ( ' --alphanum-only ' , help = ' Only allow english alpha-numeric characters and spaces/tabs/new lines ' , default = False , type = bool )
2020-04-23 09:23:57 +00:00
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 ( )
if args . control_port == 0 : args . control_port = _get_open_port ( )
send_data = bytearray ( )
recv_data = bytearray ( )
if args . connection == ' conn ' :
if not args . address :
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 ( )
2020-04-24 03:18:29 +00:00
chat ( ' conn ' , send_data , recv_data , args . alphanum_only )
2020-04-23 09:23:57 +00:00
else :
Thread ( target = connector , args = [ True , send_data , recv_data ] ,
kwargs = { ' socks_port ' : args . socks_port ,
' control_port ' : args . control_port } , daemon = True ) . start ( )
2020-04-24 03:18:29 +00:00
chat ( ' host ' , send_data , recv_data , args . alphanum_only )