198 lines
8.4 KiB
Python
198 lines
8.4 KiB
Python
#!/usr/bin/env python3.6
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
TFC - Onion-routed, endpoint secure messaging system
|
|
Copyright (C) 2013-2019 Markus Ottela
|
|
|
|
This file is part of TFC.
|
|
|
|
TFC is free software: you can redistribute it and/or modify it under the terms
|
|
of the GNU General Public License as published by the Free Software Foundation,
|
|
either version 3 of the License, or (at your option) any later version.
|
|
|
|
TFC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
PURPOSE. See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with TFC. If not, see <https://www.gnu.org/licenses/>.
|
|
"""
|
|
|
|
import time
|
|
import typing
|
|
|
|
from typing import Dict, Union
|
|
|
|
from src.common.encoding import bytes_to_int, pub_key_to_short_address
|
|
from src.common.encoding import int_to_bytes, b85encode
|
|
from src.common.exceptions import FunctionReturn
|
|
from src.common.misc import ignored, separate_header, split_byte_string
|
|
from src.common.output import rp_print
|
|
from src.common.statics import *
|
|
|
|
if typing.TYPE_CHECKING:
|
|
from datetime import datetime
|
|
from multiprocessing import Queue
|
|
from src.common.gateway import Gateway
|
|
QueueDict = Dict[bytes, Queue]
|
|
|
|
|
|
def queue_to_flask(packet: Union[bytes, str],
|
|
onion_pub_key: bytes,
|
|
flask_queue: 'Queue',
|
|
ts: 'datetime',
|
|
header: bytes
|
|
) -> None:
|
|
"""Put packet to flask queue and print message."""
|
|
p_type = {MESSAGE_DATAGRAM_HEADER: 'Message ',
|
|
PUBLIC_KEY_DATAGRAM_HEADER: 'Pub key ',
|
|
FILE_DATAGRAM_HEADER: 'File ',
|
|
GROUP_MSG_INVITE_HEADER: 'G invite ',
|
|
GROUP_MSG_JOIN_HEADER: 'G join ',
|
|
GROUP_MSG_MEMBER_ADD_HEADER: 'G add ',
|
|
GROUP_MSG_MEMBER_REM_HEADER: 'G remove ',
|
|
GROUP_MSG_EXIT_GROUP_HEADER: 'G exit '}[header]
|
|
|
|
flask_queue.put((packet, onion_pub_key))
|
|
rp_print(f"{p_type} to contact {pub_key_to_short_address(onion_pub_key)}", ts)
|
|
|
|
|
|
def src_incoming(queues: 'QueueDict',
|
|
gateway: 'Gateway',
|
|
unittest: bool = False
|
|
) -> None:
|
|
"""\
|
|
Redirect messages received from Source Computer to appropriate queues.
|
|
"""
|
|
packets_from_sc = queues[GATEWAY_QUEUE]
|
|
packets_to_dc = queues[DST_MESSAGE_QUEUE]
|
|
commands_to_dc = queues[DST_COMMAND_QUEUE]
|
|
messages_to_flask = queues[M_TO_FLASK_QUEUE]
|
|
files_to_flask = queues[F_TO_FLASK_QUEUE]
|
|
commands_to_relay = queues[SRC_TO_RELAY_QUEUE]
|
|
|
|
while True:
|
|
with ignored(EOFError, KeyboardInterrupt):
|
|
while packets_from_sc.qsize() == 0:
|
|
time.sleep(0.01)
|
|
|
|
ts, packet = packets_from_sc.get() # type: datetime, bytes
|
|
ts_bytes = int_to_bytes(int(ts.strftime('%Y%m%d%H%M%S%f')[:-4]))
|
|
|
|
try:
|
|
packet = gateway.detect_errors(packet)
|
|
except FunctionReturn:
|
|
continue
|
|
|
|
header, packet = separate_header(packet, DATAGRAM_HEADER_LENGTH)
|
|
|
|
if header == UNENCRYPTED_DATAGRAM_HEADER:
|
|
commands_to_relay.put(packet)
|
|
|
|
elif header in [COMMAND_DATAGRAM_HEADER, LOCAL_KEY_DATAGRAM_HEADER]:
|
|
commands_to_dc.put(header + ts_bytes + packet)
|
|
p_type = 'Command ' if header == COMMAND_DATAGRAM_HEADER else 'Local key'
|
|
rp_print(f"{p_type} to local Receiver", ts)
|
|
|
|
elif header in [MESSAGE_DATAGRAM_HEADER, PUBLIC_KEY_DATAGRAM_HEADER]:
|
|
onion_pub_key, payload = separate_header(packet, ONION_SERVICE_PUBLIC_KEY_LENGTH)
|
|
packet_str = header.decode() + b85encode(payload)
|
|
queue_to_flask(packet_str, onion_pub_key, messages_to_flask, ts, header)
|
|
if header == MESSAGE_DATAGRAM_HEADER:
|
|
packets_to_dc.put(header + ts_bytes + onion_pub_key + ORIGIN_USER_HEADER + payload)
|
|
|
|
elif header == FILE_DATAGRAM_HEADER:
|
|
no_contacts_b, payload = separate_header(packet, ENCODED_INTEGER_LENGTH)
|
|
no_contacts = bytes_to_int(no_contacts_b)
|
|
ser_accounts, file_ct = separate_header(payload, no_contacts * ONION_SERVICE_PUBLIC_KEY_LENGTH)
|
|
pub_keys = split_byte_string(ser_accounts, item_len=ONION_SERVICE_PUBLIC_KEY_LENGTH)
|
|
for onion_pub_key in pub_keys:
|
|
queue_to_flask(file_ct, onion_pub_key, files_to_flask, ts, header)
|
|
|
|
elif header in [GROUP_MSG_INVITE_HEADER, GROUP_MSG_JOIN_HEADER,
|
|
GROUP_MSG_MEMBER_ADD_HEADER, GROUP_MSG_MEMBER_REM_HEADER,
|
|
GROUP_MSG_EXIT_GROUP_HEADER]:
|
|
process_group_management_message(ts, packet, header, messages_to_flask)
|
|
|
|
if unittest:
|
|
break
|
|
|
|
|
|
def process_group_management_message(ts: 'datetime',
|
|
packet: bytes,
|
|
header: bytes,
|
|
messages_to_flask: 'Queue') -> None:
|
|
"""Parse and display group management message."""
|
|
header_str = header.decode()
|
|
group_id, packet = separate_header(packet, GROUP_ID_LENGTH)
|
|
|
|
if header in [GROUP_MSG_INVITE_HEADER, GROUP_MSG_JOIN_HEADER]:
|
|
pub_keys = split_byte_string(packet, ONION_SERVICE_PUBLIC_KEY_LENGTH)
|
|
for onion_pub_key in pub_keys:
|
|
others = [k for k in pub_keys if k != onion_pub_key]
|
|
packet_str = header_str + b85encode(group_id + b''.join(others))
|
|
queue_to_flask(packet_str, onion_pub_key, messages_to_flask, ts, header)
|
|
|
|
elif header in [GROUP_MSG_MEMBER_ADD_HEADER, GROUP_MSG_MEMBER_REM_HEADER]:
|
|
first_list_len_b, packet = separate_header(packet, ENCODED_INTEGER_LENGTH)
|
|
first_list_length = bytes_to_int(first_list_len_b)
|
|
pub_keys = split_byte_string(packet, ONION_SERVICE_PUBLIC_KEY_LENGTH)
|
|
before_adding = remaining = pub_keys[:first_list_length]
|
|
new_in_group = removable = pub_keys[first_list_length:]
|
|
|
|
if header == GROUP_MSG_MEMBER_ADD_HEADER:
|
|
|
|
packet_str = GROUP_MSG_MEMBER_ADD_HEADER.decode() + b85encode(group_id + b''.join(new_in_group))
|
|
for onion_pub_key in before_adding:
|
|
queue_to_flask(packet_str, onion_pub_key, messages_to_flask, ts, header)
|
|
|
|
for onion_pub_key in new_in_group:
|
|
other_new = [k for k in new_in_group if k != onion_pub_key]
|
|
packet_str = (GROUP_MSG_INVITE_HEADER.decode()
|
|
+ b85encode(group_id + b''.join(other_new + before_adding)))
|
|
queue_to_flask(packet_str, onion_pub_key, messages_to_flask, ts, header)
|
|
|
|
elif header == GROUP_MSG_MEMBER_REM_HEADER:
|
|
packet_str = header_str + b85encode(group_id + b''.join(removable))
|
|
for onion_pub_key in remaining:
|
|
queue_to_flask(packet_str, onion_pub_key, messages_to_flask, ts, header)
|
|
|
|
elif header == GROUP_MSG_EXIT_GROUP_HEADER:
|
|
pub_keys = split_byte_string(packet, ONION_SERVICE_PUBLIC_KEY_LENGTH)
|
|
packet_str = header_str + b85encode(group_id)
|
|
for onion_pub_key in pub_keys:
|
|
queue_to_flask(packet_str, onion_pub_key, messages_to_flask, ts, header)
|
|
|
|
|
|
def dst_outgoing(queues: 'QueueDict',
|
|
gateway: 'Gateway',
|
|
unittest: bool = False
|
|
) -> None:
|
|
"""Output packets from queues to Destination Computer.
|
|
|
|
Commands (and local keys) to local Destination Computer have higher
|
|
priority than messages and public keys from contacts. Prioritization
|
|
prevents contact from doing DoS on Receiver Program by filling the
|
|
queue with packets.
|
|
"""
|
|
c_queue = queues[DST_COMMAND_QUEUE]
|
|
m_queue = queues[DST_MESSAGE_QUEUE]
|
|
|
|
while True:
|
|
try:
|
|
if c_queue.qsize() == 0 and m_queue.qsize() == 0:
|
|
time.sleep(0.01)
|
|
|
|
while c_queue.qsize() != 0:
|
|
gateway.write(c_queue.get())
|
|
|
|
if m_queue.qsize() != 0:
|
|
gateway.write(m_queue.get())
|
|
|
|
if unittest and queues[UNITTEST_QUEUE].qsize() > 0:
|
|
break
|
|
|
|
except (EOFError, KeyboardInterrupt):
|
|
pass
|