Example #1
0
class TestPacketList(unittest.TestCase):

    def setUp(self):
        self.contact_list  = ContactList(nicks=['Alice', 'Bob'])
        self.settings      = Settings()
        self.onion_pub_key = nick_to_pub_key('Alice')
        packet             = Packet(self.onion_pub_key, ORIGIN_CONTACT_HEADER, MESSAGE,
                                    self.contact_list.get_contact_by_address_or_nick('Alice'), self.settings)

        self.packet_list         = PacketList(self.settings, self.contact_list)
        self.packet_list.packets = [packet]

    def test_packet_list_iterates_over_contact_objects(self):
        for p in self.packet_list:
            self.assertIsInstance(p, Packet)

    def test_len_returns_number_of_contacts(self):
        self.assertEqual(len(self.packet_list), 1)

    def test_has_packet(self):
        self.assertTrue(self.packet_list.has_packet(self.onion_pub_key, ORIGIN_CONTACT_HEADER, MESSAGE))
        self.assertFalse(self.packet_list.has_packet(self.onion_pub_key, ORIGIN_USER_HEADER, MESSAGE))

    def test_get_packet(self):
        packet = self.packet_list.get_packet(self.onion_pub_key, ORIGIN_CONTACT_HEADER, MESSAGE)
        self.assertEqual(packet.onion_pub_key, self.onion_pub_key)
        self.assertEqual(packet.origin, ORIGIN_CONTACT_HEADER)
        self.assertEqual(packet.type, MESSAGE)

        packet = self.packet_list.get_packet(self.onion_pub_key, ORIGIN_CONTACT_HEADER, MESSAGE)
        self.assertEqual(packet.onion_pub_key, self.onion_pub_key)
        self.assertEqual(packet.origin, ORIGIN_CONTACT_HEADER)
        self.assertEqual(packet.type, MESSAGE)
Example #2
0
    def setUp(self):
        self.contact_list  = ContactList(nicks=['Alice', 'Bob'])
        self.settings      = Settings()
        self.onion_pub_key = nick_to_pub_key('Alice')
        packet             = Packet(self.onion_pub_key, ORIGIN_CONTACT_HEADER, MESSAGE,
                                    self.contact_list.get_contact_by_address_or_nick('Alice'), self.settings)

        self.packet_list         = PacketList(self.settings, self.contact_list)
        self.packet_list.packets = [packet]
Example #3
0
def access_logs(window: Union['TxWindow', 'RxWindow'],
                contact_list: 'ContactList',
                group_list: 'GroupList',
                settings: 'Settings',
                master_key: 'MasterKey',
                msg_to_load: int = 0,
                export: bool = False) -> None:
    """\
    Load 'msg_to_load' last messages from log database and display or
    export them.

    The default value of zero for `msg_to_load` means all messages for
    the window will be retrieved from the log database.
    """
    file_name = f'{DIR_USER_DATA}{settings.software_operation}_logs'
    packet_list = PacketList(settings, contact_list)
    message_list = []  # type: List[MsgTuple]
    group_msg_id = b''

    check_log_file_exists(file_name)
    message_log = MessageLog(file_name, master_key.master_key)

    for log_entry in message_log:
        onion_pub_key, timestamp, origin, assembly_packet \
            = separate_headers(log_entry, [ONION_SERVICE_PUBLIC_KEY_LENGTH, TIMESTAMP_LENGTH, ORIGIN_HEADER_LENGTH])

        if window.type == WIN_TYPE_CONTACT and onion_pub_key != window.uid:
            continue

        packet = packet_list.get_packet(onion_pub_key,
                                        origin,
                                        MESSAGE,
                                        log_access=True)
        try:
            packet.add_packet(assembly_packet)
        except SoftError:
            continue
        if not packet.is_complete:
            continue

        group_msg_id = add_complete_message_to_message_list(
            timestamp, onion_pub_key, group_msg_id, packet, message_list,
            window)

    message_log.close_database()

    print_logs(message_list[-msg_to_load:], export, msg_to_load, window,
               contact_list, group_list, settings)
Example #4
0
def print_logs(message_list: List[MsgTuple],
               export:       bool,
               msg_to_load:  int,
               window:       Union['TxWindow', 'RxWindow'],
               contact_list: 'ContactList',
               group_list:   'GroupList',
               settings:     'Settings'
               ) -> None:
    """Print list of logged messages to screen or export them to file."""
    terminal_width = get_terminal_width()
    system, m_dir  = {TX: ("Transmitter", "sent to"),
                      RX: ("Receiver",    "to/from")}[settings.software_operation]

    f_name = open(f"{system} - Plaintext log ({window.name})", 'w+') if export else sys.stdout
    subset = '' if msg_to_load == 0 else f"{msg_to_load} most recent "
    title  = textwrap.fill(f"Log file of {subset}message(s) {m_dir} {window.type} {window.name}", terminal_width)

    packet_list            = PacketList(settings, contact_list)
    log_window             = RxWindow(window.uid, contact_list, group_list, settings, packet_list)
    log_window.is_active   = True
    log_window.message_log = message_list

    if message_list:
        if not export:
            clear_screen()
        print(title,                 file=f_name)
        print(terminal_width * '═',  file=f_name)
        log_window.redraw(           file=f_name)
        print("<End of log file>\n", file=f_name)
    else:
        raise FunctionReturn(f"No logged messages for {window.type} '{window.name}'.", head_clear=True)

    if export:
        f_name.close()
Example #5
0
    def setUp(self):
        self.unittest_dir = cd_unittest()

        self.msg = ("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean condimentum consectetur purus quis"
                    " dapibus. Fusce venenatis lacus ut rhoncus faucibus. Cras sollicitudin commodo sapien, sed bibendu"
                    "m velit maximus in. Aliquam ac metus risus. Sed cursus ornare luctus. Integer aliquet lectus id ma"
                    "ssa blandit imperdiet. Ut sed massa eget quam facilisis rutrum. Mauris eget luctus nisl. Sed ut el"
                    "it iaculis, faucibus lacus eget, sodales magna. Nunc sed commodo arcu. In hac habitasse platea dic"
                    "tumst. Integer luctus aliquam justo, at vestibulum dolor iaculis ac. Etiam laoreet est eget odio r"
                    "utrum, vel malesuada lorem rhoncus. Cras finibus in neque eu euismod. Nulla facilisi. Nunc nec ali"
                    "quam quam, quis ullamcorper leo. Nunc egestas lectus eget est porttitor, in iaculis felis sceleris"
                    "que. In sem elit, fringilla id viverra commodo, sagittis varius purus. Pellentesque rutrum loborti"
                    "s neque a facilisis. Mauris id tortor placerat, aliquam dolor ac, venenatis arcu.")

        self.ts         = datetime.now()
        self.master_key = MasterKey()
        self.settings   = Settings(log_file_masking=True)
        self.file_name  = f'{DIR_USER_DATA}{self.settings.software_operation}_logs'

        self.contact_list = ContactList(nicks=['Alice', 'Bob', 'Charlie', LOCAL_ID])
        self.key_list     = KeyList(    nicks=['Alice', 'Bob', 'Charlie', LOCAL_ID])
        self.group_list   = GroupList( groups=['test_group'])
        self.packet_list  = PacketList(contact_list=self.contact_list, settings=self.settings)
        self.window_list  = WindowList(contact_list=self.contact_list, settings=self.settings, 
                                       group_list=self.group_list, packet_list=self.packet_list)
        self.group_id     = group_name_to_group_id('test_group')
        self.file_keys    = dict()

        self.group_list.get_group('test_group').log_messages = True
        self.args = (self.window_list, self.packet_list, self.contact_list, self.key_list, 
                     self.group_list, self.settings, self.master_key, self.file_keys)

        ensure_dir(DIR_USER_DATA)
Example #6
0
    def setUp(self):
        self.unittest_dir = cd_unittest()
        self.ts           = datetime.now()
        self.settings     = Settings()
        self.master_key   = MasterKey()
        self.group_list   = GroupList()
        self.exit_queue   = Queue()
        self.gateway      = Gateway()
        self.window_list  = WindowList(nicks=[LOCAL_ID])
        self.contact_list = ContactList(nicks=[LOCAL_ID])
        self.packet_list  = PacketList(self.settings, self.contact_list)
        self.key_list     = KeyList(nicks=[LOCAL_ID])
        self.key_set      = self.key_list.get_keyset(LOCAL_PUBKEY)

        self.args = (self.window_list, self.packet_list, self.contact_list, self.key_list, self.group_list,
                     self.settings, self.master_key, self.gateway, self.exit_queue)
Example #7
0
def remove_logs(contact_list: 'ContactList', group_list: 'GroupList',
                settings: 'Settings', master_key: 'MasterKey',
                selector: bytes) -> None:
    """\
    Remove log entries for selector (public key of an account/group ID).

    If the selector is a public key, all messages (both the private
    conversation and any associated group messages) sent to and received
    from the associated contact are removed. If the selector is a group
    ID, only messages for the group matching that group ID are removed.
    """
    ensure_dir(DIR_USER_DATA)
    file_name = f'{DIR_USER_DATA}{settings.software_operation}_logs'
    temp_name = file_name + TEMP_SUFFIX
    packet_list = PacketList(settings, contact_list)
    entries_to_keep = []  # type: List[bytes]
    removed = False
    contact = len(selector) == ONION_SERVICE_PUBLIC_KEY_LENGTH

    check_log_file_exists(file_name)
    message_log = MessageLog(file_name, master_key.master_key)

    for log_entry in message_log:

        onion_pub_key, _, origin, assembly_packet = separate_headers(
            log_entry, [
                ONION_SERVICE_PUBLIC_KEY_LENGTH, TIMESTAMP_LENGTH,
                ORIGIN_HEADER_LENGTH
            ])
        if contact:
            if onion_pub_key == selector:
                removed = True
            else:
                entries_to_keep.append(log_entry)

        else:  # Group
            packet = packet_list.get_packet(onion_pub_key,
                                            origin,
                                            MESSAGE,
                                            log_access=True)
            try:
                packet.add_packet(assembly_packet, log_entry)
            except SoftError:
                continue
            if not packet.is_complete:
                continue

            removed = check_packet_fate(entries_to_keep, packet, removed,
                                        selector)

    message_log.close_database()

    message_log_temp = MessageLog(temp_name, master_key.master_key)

    for log_entry in entries_to_keep:
        message_log_temp.insert_log_entry(log_entry)
    message_log_temp.close_database()

    os.replace(temp_name, file_name)

    try:
        name = contact_list.get_nick_by_pub_key(
            selector) if contact else group_list.get_group_by_id(selector).name
    except StopIteration:
        name = pub_key_to_short_address(selector) if contact else b58encode(
            selector)

    action = "Removed" if removed else "Found no"
    win_type = "contact" if contact else "group"

    raise SoftError(f"{action} log entries for {win_type} '{name}'.")
Example #8
0
def remove_logs(contact_list: 'ContactList', group_list: 'GroupList',
                settings: 'Settings', master_key: 'MasterKey',
                selector: bytes) -> None:
    """\
    Remove log entries for selector (public key of an account/group ID).

    If the selector is a public key, all messages (both the private
    conversation and any associated group messages) sent to and received
    from the associated contact are removed. If the selector is a group
    ID, only messages for group determined by that group ID are removed.
    """
    ensure_dir(DIR_USER_DATA)
    file_name = f'{DIR_USER_DATA}{settings.software_operation}_logs'
    temp_name = f'{file_name}_temp'
    log_file = get_logfile(file_name)
    packet_list = PacketList(settings, contact_list)
    ct_to_keep = []  # type: List[bytes]
    removed = False
    contact = len(selector) == ONION_SERVICE_PUBLIC_KEY_LENGTH

    for ct in iter(lambda: log_file.read(LOG_ENTRY_LENGTH), b''):
        plaintext = auth_and_decrypt(ct,
                                     master_key.master_key,
                                     database=file_name)

        onion_pub_key, _, origin, assembly_packet = separate_headers(
            plaintext, [
                ONION_SERVICE_PUBLIC_KEY_LENGTH, TIMESTAMP_LENGTH,
                ORIGIN_HEADER_LENGTH
            ])
        if contact:
            if onion_pub_key == selector:
                removed = True
            else:
                ct_to_keep.append(ct)

        else:  # Group
            packet = packet_list.get_packet(onion_pub_key,
                                            origin,
                                            MESSAGE,
                                            log_access=True)
            try:
                packet.add_packet(assembly_packet, ct)
            except FunctionReturn:
                continue
            if not packet.is_complete:
                continue

            _, header, message = separate_headers(
                packet.assemble_message_packet(),
                [WHISPER_FIELD_LENGTH, MESSAGE_HEADER_LENGTH])

            if header == PRIVATE_MESSAGE_HEADER:
                ct_to_keep.extend(packet.log_ct_list)
                packet.clear_assembly_packets()

            elif header == GROUP_MESSAGE_HEADER:
                group_id, _ = separate_header(message, GROUP_ID_LENGTH)
                if group_id == selector:
                    removed = True
                else:
                    ct_to_keep.extend(packet.log_ct_list)
                    packet.clear_assembly_packets()

    log_file.close()

    if os.path.isfile(temp_name):
        os.remove(temp_name)

    with open(temp_name, 'wb+') as f:
        if ct_to_keep:
            f.write(b''.join(ct_to_keep))

    os.remove(file_name)
    os.rename(temp_name, file_name)

    try:
        name = contact_list.get_contact_by_pub_key(selector).nick \
            if contact else group_list.get_group_by_id(selector).name
    except StopIteration:
        name = pub_key_to_short_address(selector) \
            if contact else b58encode(selector)

    action = "Removed" if removed else "Found no"
    win_type = "contact" if contact else "group"

    raise FunctionReturn(f"{action} log entries for {win_type} '{name}'.")
Example #9
0
def access_logs(window: Union['TxWindow', 'RxWindow'],
                contact_list: 'ContactList',
                group_list: 'GroupList',
                settings: 'Settings',
                master_key: 'MasterKey',
                msg_to_load: int = 0,
                export: bool = False) -> None:
    """\
    Load 'msg_to_load' last messages from log database and display or
    export them.

    The default value of zero for `msg_to_load` means all messages for
    the window will be retrieved from the log database.
    """
    file_name = f'{DIR_USER_DATA}{settings.software_operation}_logs'
    log_file = get_logfile(file_name)
    packet_list = PacketList(settings, contact_list)
    message_log = []  # type: List[MsgTuple]
    group_msg_id = b''

    for ct in iter(lambda: log_file.read(LOG_ENTRY_LENGTH), b''):
        plaintext = auth_and_decrypt(ct,
                                     master_key.master_key,
                                     database=file_name)

        onion_pub_key, timestamp, origin, assembly_packet = separate_headers(
            plaintext, [
                ONION_SERVICE_PUBLIC_KEY_LENGTH, TIMESTAMP_LENGTH,
                ORIGIN_HEADER_LENGTH
            ])
        if window.type == WIN_TYPE_CONTACT and onion_pub_key != window.uid:
            continue

        packet = packet_list.get_packet(onion_pub_key,
                                        origin,
                                        MESSAGE,
                                        log_access=True)
        try:
            packet.add_packet(assembly_packet)
        except FunctionReturn:
            continue
        if not packet.is_complete:
            continue

        whisper_byte, header, message = separate_headers(
            packet.assemble_message_packet(),
            [WHISPER_FIELD_LENGTH, MESSAGE_HEADER_LENGTH])
        whisper = bytes_to_bool(whisper_byte)

        if header == PRIVATE_MESSAGE_HEADER and window.type == WIN_TYPE_CONTACT:
            message_log.append(
                (bytes_to_timestamp(timestamp), message.decode(),
                 onion_pub_key, packet.origin, whisper, False))

        elif header == GROUP_MESSAGE_HEADER and window.type == WIN_TYPE_GROUP:
            purp_group_id, message = separate_header(message, GROUP_ID_LENGTH)
            if window.group is not None and purp_group_id != window.group.group_id:
                continue

            purp_msg_id, message = separate_header(message,
                                                   GROUP_MSG_ID_LENGTH)
            if packet.origin == ORIGIN_USER_HEADER:
                if purp_msg_id == group_msg_id:
                    continue
                group_msg_id = purp_msg_id

            message_log.append(
                (bytes_to_timestamp(timestamp), message.decode(),
                 onion_pub_key, packet.origin, whisper, False))

    log_file.close()

    print_logs(message_log[-msg_to_load:], export, msg_to_load, window,
               contact_list, group_list, settings)
Example #10
0
def output_loop(queues: Dict[bytes, 'Queue[Any]'],
                gateway: 'Gateway',
                settings: 'Settings',
                contact_list: 'ContactList',
                key_list: 'KeyList',
                group_list: 'GroupList',
                master_key: 'MasterKey',
                message_log: 'MessageLog',
                stdin_fd: int,
                unit_test: bool = False) -> None:
    """Process packets in message queues according to their priority."""
    sys.stdin = os.fdopen(stdin_fd)
    packet_buffer = dict()  # type: packet_buffer_type
    file_buffer = dict()  # type: file_buffer_type
    file_keys = dict()  # type: file_keys_type

    kdk_hashes = []  # type: List[bytes]
    packet_hashes = []  # type: List[bytes]

    packet_list = PacketList(settings, contact_list)
    window_list = WindowList(settings, contact_list, group_list, packet_list)

    clear_screen()
    while True:
        try:
            # Local key packets
            process_local_key_queue(queues, window_list, contact_list,
                                    key_list, settings, kdk_hashes,
                                    packet_hashes)

            # Commands
            process_command_queue(queues, window_list, contact_list,
                                  group_list, settings, key_list, packet_list,
                                  master_key, gateway)

            # File window refresh
            window_list.refresh_file_window_check()

            # Cached messages
            process_cached_messages(window_list, contact_list, group_list,
                                    key_list, settings, packet_list,
                                    message_log, file_keys, packet_buffer)

            # New messages
            process_message_queue(queues, window_list, contact_list,
                                  group_list, key_list, settings, packet_list,
                                  message_log, file_keys, packet_buffer)

            # Cached files
            process_cached_files(window_list, contact_list, settings,
                                 file_keys, file_buffer)

            # New files
            process_file_queue(queues, window_list, contact_list, settings,
                               file_keys, file_buffer)

            time.sleep(0.01)

            if unit_test and queues[UNIT_TEST_QUEUE].qsize() != 0:
                break

        except (KeyError, KeyboardInterrupt, SoftError):
            pass
Example #11
0
def output_loop(queues: Dict[bytes, 'Queue[Any]'],
                gateway: 'Gateway',
                settings: 'Settings',
                contact_list: 'ContactList',
                key_list: 'KeyList',
                group_list: 'GroupList',
                master_key: 'MasterKey',
                stdin_fd: int,
                unit_test: bool = False) -> None:
    """Process packets in message queues according to their priority."""
    local_key_queue = queues[LOCAL_KEY_DATAGRAM_HEADER]
    message_queue = queues[MESSAGE_DATAGRAM_HEADER]
    file_queue = queues[FILE_DATAGRAM_HEADER]
    command_queue = queues[COMMAND_DATAGRAM_HEADER]
    exit_queue = queues[EXIT_QUEUE]

    sys.stdin = os.fdopen(stdin_fd)
    packet_buffer = dict()  # type: Dict[bytes, List[Tuple[datetime, bytes]]]
    file_buffer = dict()  # type: Dict[bytes, Tuple[datetime, bytes]]
    file_keys = dict()  # type: Dict[bytes, bytes]

    kdk_hashes = []  # type: List[bytes]
    packet_hashes = []  # type: List[bytes]

    packet_list = PacketList(settings, contact_list)
    window_list = WindowList(settings, contact_list, group_list, packet_list)

    clear_screen()
    while True:
        try:
            if local_key_queue.qsize() != 0:
                ts, packet = local_key_queue.get()
                process_local_key(ts, packet, window_list, contact_list,
                                  key_list, settings, kdk_hashes,
                                  packet_hashes, local_key_queue)
                continue

            if not contact_list.has_local_contact():
                time.sleep(0.1)
                continue

            # Commands
            if command_queue.qsize() != 0:
                ts, packet = command_queue.get()
                process_command(ts, packet, window_list, packet_list,
                                contact_list, key_list, group_list, settings,
                                master_key, gateway, exit_queue)
                continue

            # File window refresh
            if window_list.active_win is not None and window_list.active_win.uid == WIN_UID_FILE:
                window_list.active_win.redraw_file_win()

            # Cached message packets
            for onion_pub_key in packet_buffer:
                if (contact_list.has_pub_key(onion_pub_key)
                        and key_list.has_rx_mk(onion_pub_key)
                        and packet_buffer[onion_pub_key]):
                    ts, packet = packet_buffer[onion_pub_key].pop(0)
                    process_message(ts, packet, window_list, packet_list,
                                    contact_list, key_list, group_list,
                                    settings, master_key, file_keys)
                    continue

            # New messages
            if message_queue.qsize() != 0:
                ts, packet = message_queue.get()
                onion_pub_key = packet[:ONION_SERVICE_PUBLIC_KEY_LENGTH]

                if contact_list.has_pub_key(
                        onion_pub_key) and key_list.has_rx_mk(onion_pub_key):
                    process_message(ts, packet, window_list, packet_list,
                                    contact_list, key_list, group_list,
                                    settings, master_key, file_keys)
                else:
                    packet_buffer.setdefault(onion_pub_key, []).append(
                        (ts, packet))
                continue

            # Cached files
            if file_buffer:
                for k in file_buffer:
                    key_to_remove = b''
                    try:
                        if k in file_keys:
                            key_to_remove = k
                            ts_, file_ct = file_buffer[k]
                            dec_key = file_keys[k]
                            onion_pub_key = k[:ONION_SERVICE_PUBLIC_KEY_LENGTH]
                            process_file(ts_, onion_pub_key, file_ct, dec_key,
                                         contact_list, window_list, settings)
                    finally:
                        if key_to_remove:
                            file_buffer.pop(k)
                            file_keys.pop(k)
                            break

            # New files
            if file_queue.qsize() != 0:
                ts, packet = file_queue.get()
                new_file(ts, packet, file_keys, file_buffer, contact_list,
                         window_list, settings)

            time.sleep(0.01)

            if unit_test and queues[UNIT_TEST_QUEUE].qsize() != 0:
                break

        except (FunctionReturn, KeyError, KeyboardInterrupt):
            pass