From 918d5af851441a1639323c7b9cfabdb37adec348 Mon Sep 17 00:00:00 2001 From: hhh Date: Mon, 27 May 2024 17:12:21 +0300 Subject: [PATCH] Autoformat --- dragonion_server/__main__.py | 3 +- dragonion_server/cli/__init__.py | 5 +- dragonion_server/cli/cmd/service/remove.py | 18 +- dragonion_server/cli/cmd/service/run.py | 37 ++-- dragonion_server/cli/cmd/service/service.py | 15 +- dragonion_server/cli/cmd/service/write.py | 16 +- dragonion_server/cli/common.py | 9 +- dragonion_server/cli/utils/__init__.py | 5 +- dragonion_server/cli/utils/groups.py | 7 +- dragonion_server/modules/server/__init__.py | 9 +- .../server/handlers/managers/connection.py | 21 +-- .../modules/server/handlers/managers/room.py | 159 ++++++++---------- .../server/handlers/managers/service.py | 31 ++-- .../server/handlers/websocket_server.py | 14 +- .../modules/server/integration.py | 34 ++-- dragonion_server/modules/server/routes.py | 2 +- dragonion_server/modules/server/server.py | 24 +-- dragonion_server/utils/config/__init__.py | 5 +- dragonion_server/utils/config/db.py | 10 +- dragonion_server/utils/config/models.py | 4 +- dragonion_server/utils/core/dirs.py | 29 ++-- dragonion_server/utils/onion/__init__.py | 8 +- dragonion_server/utils/onion/onion.py | 89 +++++----- dragonion_server/utils/onion/stem_process.py | 118 ++++++++----- .../utils/onion/tor_downloader.py | 64 +++---- 25 files changed, 358 insertions(+), 378 deletions(-) diff --git a/dragonion_server/__main__.py b/dragonion_server/__main__.py index e6935ec..4acb543 100644 --- a/dragonion_server/__main__.py +++ b/dragonion_server/__main__.py @@ -1,5 +1,4 @@ from dragonion_server import main - -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/dragonion_server/cli/__init__.py b/dragonion_server/cli/__init__.py index e82a8ca..a7aa4c9 100644 --- a/dragonion_server/cli/__init__.py +++ b/dragonion_server/cli/__init__.py @@ -1,6 +1,3 @@ from .common import cli - -__all__ = [ - 'cli' -] +__all__ = ["cli"] diff --git a/dragonion_server/cli/cmd/service/remove.py b/dragonion_server/cli/cmd/service/remove.py index 31ef889..7f6dc93 100644 --- a/dragonion_server/cli/cmd/service/remove.py +++ b/dragonion_server/cli/cmd/service/remove.py @@ -2,34 +2,34 @@ import os import click -from dragonion_server.utils.config import db from dragonion_server.common import console +from dragonion_server.utils.config import db class ServiceRemoveCommand(click.Command): def __init__(self): super().__init__( - name='remove', + name="remove", callback=self.callback, params=[ click.Option( - ('--name', '-n'), + ("--name", "-n"), required=True, prompt=True, type=str, - help='Name of service to write to' + help="Name of service to write to", ) - ] + ], ) @staticmethod def callback(name: str): try: del db.services[name] - if os.path.isfile(f'{name}.auth'): - os.remove(f'{name}.auth') - - print(f'Removed service {name}') + if os.path.isfile(f"{name}.auth"): + os.remove(f"{name}.auth") + + print(f"Removed service {name}") except KeyError: print(f'Service "{name}" does not exist in this storage') except Exception as e: diff --git a/dragonion_server/cli/cmd/service/run.py b/dragonion_server/cli/cmd/service/run.py index 3ff57c4..9444403 100644 --- a/dragonion_server/cli/cmd/service/run.py +++ b/dragonion_server/cli/cmd/service/run.py @@ -1,60 +1,63 @@ import sys + import click -from dragonion_server.modules.server import run, run_without_onion, integrate_onion from dragonion_server.common import console +from dragonion_server.modules.server import integrate_onion, run, run_without_onion class ServiceRunCommand(click.Command): def __init__(self): super().__init__( - name='run', + name="run", callback=self.callback, params=[ click.Option( - ('--name', '-n'), + ("--name", "-n"), required=True, prompt=True, type=str, - help='Name of service to write to' + help="Name of service to write to", ), click.Option( - ('--port', '-p'), + ("--port", "-p"), required=False, prompt=True, prompt_required=False, type=int, - help='Port to start service on' + help="Port to start service on", ), click.Option( - ('--without-tor', '-wt'), + ("--without-tor", "-wt"), is_flag=True, - help='Run service without tor' + help="Run service without tor", ), click.Option( - ('--only-tor', '-ot'), + ("--only-tor", "-ot"), is_flag=True, - help='Run only tor proxy to service' - ) - ] + help="Run only tor proxy to service", + ), + ], ) @staticmethod def callback(name: str, port: int | None, without_tor: bool, only_tor: bool): try: if without_tor and only_tor: - print('Cannot run only tor without tor, exiting') + print("Cannot run only tor without tor, exiting") sys.exit(1) elif without_tor: run_without_onion(name, port) elif only_tor: if port is None: - print('For this mode, you need to specify port, ' - 'to which requests will be redirected. Cannot start ' - 'tor service, exiting') + print( + "For this mode, you need to specify port, " + "to which requests will be redirected. Cannot start " + "tor service, exiting" + ) sys.exit(1) onion = integrate_onion(port, name) - input('Press Enter to stop onion and service...') + input("Press Enter to stop onion and service...") onion.cleanup() else: run(name, port) diff --git a/dragonion_server/cli/cmd/service/service.py b/dragonion_server/cli/cmd/service/service.py index 75fc2ff..ef58b3e 100644 --- a/dragonion_server/cli/cmd/service/service.py +++ b/dragonion_server/cli/cmd/service/service.py @@ -1,14 +1,13 @@ from ...utils import ModuleGroup -from .write import ServiceWriteCommand -from .run import ServiceRunCommand from .remove import ServiceRemoveCommand - +from .run import ServiceRunCommand +from .write import ServiceWriteCommand service_group = ModuleGroup( - name='service', + name="service", commands={ - 'write': ServiceWriteCommand(), - 'run': ServiceRunCommand(), - 'remove': ServiceRemoveCommand() - } + "write": ServiceWriteCommand(), + "run": ServiceRunCommand(), + "remove": ServiceRemoveCommand(), + }, ) diff --git a/dragonion_server/cli/cmd/service/write.py b/dragonion_server/cli/cmd/service/write.py index 5e70b3c..bec4abc 100644 --- a/dragonion_server/cli/cmd/service/write.py +++ b/dragonion_server/cli/cmd/service/write.py @@ -1,30 +1,30 @@ import click -from dragonion_server.utils.onion import Onion from dragonion_server.common import console +from dragonion_server.utils.onion import Onion class ServiceWriteCommand(click.Command): def __init__(self): super().__init__( - name='write', + name="write", callback=self.callback, params=[ click.Option( - ('--name', '-n'), + ("--name", "-n"), required=True, prompt=True, type=str, - help='Name of service to write to' + help="Name of service to write to", ), click.Option( - ('--port', '-p'), + ("--port", "-p"), required=True, prompt=True, type=int, - help='Port to start service on' - ) - ] + help="Port to start service on", + ), + ], ) @staticmethod diff --git a/dragonion_server/cli/common.py b/dragonion_server/cli/common.py index 55bc9e0..c9ebcf3 100644 --- a/dragonion_server/cli/common.py +++ b/dragonion_server/cli/common.py @@ -1,10 +1,5 @@ import click + from .cmd.service.service import service_group - -cli = click.CommandCollection( - name='dragonion-server', - sources=[ - service_group() - ] -) +cli = click.CommandCollection(name="dragonion-server", sources=[service_group()]) diff --git a/dragonion_server/cli/utils/__init__.py b/dragonion_server/cli/utils/__init__.py index 226d155..7e5b986 100644 --- a/dragonion_server/cli/utils/__init__.py +++ b/dragonion_server/cli/utils/__init__.py @@ -1,6 +1,3 @@ from .groups import ModuleGroup - -__all__ = [ - 'ModuleGroup' -] +__all__ = ["ModuleGroup"] diff --git a/dragonion_server/cli/utils/groups.py b/dragonion_server/cli/utils/groups.py index 08324d7..76b616e 100644 --- a/dragonion_server/cli/utils/groups.py +++ b/dragonion_server/cli/utils/groups.py @@ -1,6 +1,7 @@ -import click import typing as t +import click + class ModuleGroup(click.Group): def __init__( @@ -13,6 +14,6 @@ class ModuleGroup(click.Group): ) -> None: new_commands = dict() for command_key in commands.keys(): - new_commands[f'{name}-{command_key}'] = commands[command_key] - + new_commands[f"{name}-{command_key}"] = commands[command_key] + super().__init__(name, new_commands, **attrs) diff --git a/dragonion_server/modules/server/__init__.py b/dragonion_server/modules/server/__init__.py index 9ac0db8..075116b 100644 --- a/dragonion_server/modules/server/__init__.py +++ b/dragonion_server/modules/server/__init__.py @@ -1,9 +1,4 @@ -from .server import run, run_without_onion from .integration import integrate_onion +from .server import run, run_without_onion - -__all__ = [ - 'run', - 'run_without_onion', - 'integrate_onion' -] +__all__ = ["run", "run_without_onion", "integrate_onion"] diff --git a/dragonion_server/modules/server/handlers/managers/connection.py b/dragonion_server/modules/server/handlers/managers/connection.py index ab2622b..bf7a5b4 100644 --- a/dragonion_server/modules/server/handlers/managers/connection.py +++ b/dragonion_server/modules/server/handlers/managers/connection.py @@ -1,11 +1,11 @@ from attrs import define -from fastapi import WebSocket from dragonion_core.proto.web.webmessage import ( + WebErrorMessage, set_time, - webmessages_union, webmessage_error_message_literal, - WebErrorMessage + webmessages_union, ) +from fastapi import WebSocket @define @@ -19,21 +19,14 @@ class Connection(object): """ Sends WebMessage object to this connection :param obj: Should be some type of WebMessage - :return: + :return: """ await self.ws.send_text(set_time(obj).to_json()) - async def send_error( - self, - error_message: webmessage_error_message_literal - ): + async def send_error(self, error_message: webmessage_error_message_literal): """ Sends error with specified messages :param error_message: See webmessage_error_message_literal for available - :return: + :return: """ - await self.send_webmessage( - WebErrorMessage( - error_message=error_message - ) - ) + await self.send_webmessage(WebErrorMessage(error_message=error_message)) diff --git a/dragonion_server/modules/server/handlers/managers/room.py b/dragonion_server/modules/server/handlers/managers/room.py index 6686aa9..f65b317 100644 --- a/dragonion_server/modules/server/handlers/managers/room.py +++ b/dragonion_server/modules/server/handlers/managers/room.py @@ -1,25 +1,24 @@ -from attrs import define -from .connection import Connection -from .exceptions import GotInvalidWebmessage +from datetime import datetime +from json.decoder import JSONDecodeError from typing import Dict + +from attrs import define +from dragonion_core.proto.web.webmessage import ( + WebBroadcastableMessage, + WebConnectionMessage, + WebConnectionResultMessage, + WebDisconnectMessage, + WebErrorMessage, + WebMessageMessage, + WebNotificationMessage, + set_time, + webmessage_error_message_literal, + webmessages_union, +) from fastapi import WebSocket -from json.decoder import JSONDecodeError - -from dragonion_core.proto.web.webmessage import ( - webmessages_union, - set_time, - WebMessageMessage, - WebBroadcastableMessage, - WebNotificationMessage, - webmessage_error_message_literal, - WebErrorMessage, - WebConnectionMessage, - WebDisconnectMessage, - WebConnectionResultMessage -) - -from datetime import datetime +from .connection import Connection +from .exceptions import GotInvalidWebmessage @define @@ -28,62 +27,65 @@ class Room(object): async def accept_connection(self, ws: WebSocket) -> Connection | None: """ - Accepts connection, checks username availability and adds it to dict of - connections - :param ws: Websocket of connection - :return: + Accepts connection, checks username availability and adds it to dict of + connections + :param ws: Websocket of connection + :return: """ - print('Incoming connection') + print("Incoming connection") await ws.accept() try: - connection_message = WebConnectionMessage.from_json( - await ws.receive_text() - ) + connection_message = WebConnectionMessage.from_json(await ws.receive_text()) except JSONDecodeError: - await ws.send_text(set_time(WebErrorMessage( - 'invalid_webmessage' - )).to_json()) - await ws.close(reason='invalid_webmessage') - return - + await ws.send_text( + set_time(WebErrorMessage("invalid_webmessage")).to_json() + ) + await ws.close(reason="invalid_webmessage") + return + connection = Connection( username=connection_message.username, ws=ws, public_key=connection_message.public_key, - password=connection_message.password + password=connection_message.password, ) - + if connection_message.username in self.connections.keys(): - await connection.send_error( - 'username_exists' - ) - await ws.close(reason='username_exists') - return + await connection.send_error("username_exists") + await ws.close(reason="username_exists") + return self.connections[connection_message.username] = connection - await connection.send_webmessage(WebConnectionResultMessage( - connected_users=dict( - map( - lambda i, j: (i, j), - [_username for _username in list(self.connections.keys()) - if self.connections[_username].password == - connection_message.password], - [_connection.public_key for _connection - in self.connections.values() if _connection.password == - connection_message.password] + await connection.send_webmessage( + WebConnectionResultMessage( + connected_users=dict( + map( + lambda i, j: (i, j), + [ + _username + for _username in list(self.connections.keys()) + if self.connections[_username].password + == connection_message.password + ], + [ + _connection.public_key + for _connection in self.connections.values() + if _connection.password == connection_message.password + ], + ) ) ) - )) + ) await self.broadcast_webmessage(connection_message) - print(f'[{datetime.now().time()}] Accepted {connection_message.username}') + print(f"[{datetime.now().time()}] Accepted {connection_message.username}") return connection async def broadcast_webmessage(self, obj: webmessages_union): """ Broadcasts WebMessages to all connections in room - :param obj: - :return: + :param obj: + :return: """ for connection in self.connections.values(): await connection.send_webmessage(obj) @@ -91,11 +93,11 @@ class Room(object): async def broadcast_message(self, broadcastable: WebBroadcastableMessage): """ Broadcasts message to every user in room - :param broadcastable: String object with json representation of + :param broadcastable: String object with json representation of WebBroadcastableMessage - :return: + :return: """ - try: + try: for to_username in broadcastable.messages.keys(): try: await self.connections[to_username].send_webmessage( @@ -110,28 +112,17 @@ class Room(object): """ Broadcasts notification from server :param message: Content - :return: + :return: """ - await self.broadcast_webmessage( - WebNotificationMessage( - message=message - ) - ) + await self.broadcast_webmessage(WebNotificationMessage(message=message)) - async def broadcast_error( - self, - error_message: webmessage_error_message_literal - ): + async def broadcast_error(self, error_message: webmessage_error_message_literal): """ Broadcasts server error :param error_message: See webmessage_error_message_literal - :return: + :return: """ - await self.broadcast_webmessage( - WebErrorMessage( - error_message=error_message - ) - ) + await self.broadcast_webmessage(WebErrorMessage(error_message=error_message)) async def broadcast_user_disconnected(self, username: str): """ @@ -139,18 +130,14 @@ class Room(object): :param username: Username of user that disconnected :return: """ - await self.broadcast_webmessage( - WebDisconnectMessage( - username=username - ) - ) + await self.broadcast_webmessage(WebDisconnectMessage(username=username)) async def get_connection_by(self, attribute: str, value: str) -> Connection | None: """ Search for connection by attribute and value in it - :param attribute: - :param value: - :return: + :param attribute: + :param value: + :return: """ for connection in self.connections.values(): if getattr(connection, attribute) == value: @@ -158,11 +145,11 @@ class Room(object): async def disconnect(self, connection: Connection, close_reason: str | None = None): """ - Disconnects by connection object. - :param connection: Object of connection. + Disconnects by connection object. + :param connection: Object of connection. It can be obtained using get_connection_by :param close_reason: Reason if exists - :return: + :return: """ if connection not in self.connections.values(): return @@ -170,9 +157,7 @@ class Room(object): del self.connections[connection.username] try: - await connection.ws.close( - reason=close_reason - ) + await connection.ws.close(reason=close_reason) except Exception as e: assert e diff --git a/dragonion_server/modules/server/handlers/managers/service.py b/dragonion_server/modules/server/handlers/managers/service.py index d375793..7cbca35 100644 --- a/dragonion_server/modules/server/handlers/managers/service.py +++ b/dragonion_server/modules/server/handlers/managers/service.py @@ -1,10 +1,9 @@ -from .connection import Connection -from .room import Room from typing import Dict -from dragonion_core.proto.web.webmessage import ( - webmessage_error_message_literal -) +from dragonion_core.proto.web.webmessage import webmessage_error_message_literal + +from .connection import Connection +from .room import Room class Service(object): @@ -21,10 +20,7 @@ class Service(object): for room in self.rooms.values(): await room.broadcast_notification(message) - async def broadcast_error( - self, - error_message: webmessage_error_message_literal - ): + async def broadcast_error(self, error_message: webmessage_error_message_literal): for room in self.rooms.values(): await room.broadcast_error(error_message) @@ -32,31 +28,31 @@ class Service(object): """ Searches for room by valid connection object in it :param connection: Connection in unknown room to search - :return: + :return: """ for room in self.rooms.values(): if connection in room.connections.values(): return room async def get_connection_by_attribute( - self, attribute: str, value: str + self, attribute: str, value: str ) -> Connection: """ Gets connection in some room by attribute and value in it - :param attribute: - :param value: - :return: + :param attribute: + :param value: + :return: """ for room in self.rooms.values(): if connection := await room.get_connection_by(attribute, value): return connection - async def close_room(self, room_name: str, reason: str = 'Unknown reason'): + async def close_room(self, room_name: str, reason: str = "Unknown reason"): """ Closes all connections in room :param room_name: Close name :param reason: Reason to close room, default is Unknown reason - :return: + :return: """ room = self.rooms.get(room_name) if room is None: @@ -64,6 +60,5 @@ class Service(object): for connection in room.connections.values(): await room.disconnect( - connection=connection, - close_reason=f'Room is closed: {reason}' + connection=connection, close_reason=f"Room is closed: {reason}" ) diff --git a/dragonion_server/modules/server/handlers/websocket_server.py b/dragonion_server/modules/server/handlers/websocket_server.py index 53d3646..9a02ec7 100644 --- a/dragonion_server/modules/server/handlers/websocket_server.py +++ b/dragonion_server/modules/server/handlers/websocket_server.py @@ -1,13 +1,11 @@ -from fastapi import WebSocket, WebSocketDisconnect -from .managers.service import Service -from dragonion_core.proto.web.webmessage import ( - webmessages_union, - WebMessage -) -from .managers.exceptions import GotInvalidWebmessage - from datetime import datetime +from dragonion_core.proto.web.webmessage import WebMessage, webmessages_union +from fastapi import WebSocket, WebSocketDisconnect + +from .managers.exceptions import GotInvalidWebmessage +from .managers.service import Service + service = Service() diff --git a/dragonion_server/modules/server/integration.py b/dragonion_server/modules/server/integration.py index b373933..fd2edcd 100644 --- a/dragonion_server/modules/server/integration.py +++ b/dragonion_server/modules/server/integration.py @@ -1,15 +1,15 @@ import sys -from dragonion_server.utils.onion import Onion from dragonion_core.proto.file import AuthFile -from dragonion_server.utils.config.db import services - from rich import print +from dragonion_server.utils.config.db import services +from dragonion_server.utils.onion import Onion + def integrate_onion(port: int, name: str) -> Onion: """ - Starts onion service, writes it to config + Starts onion service, writes it to config :param port: Port, where local service is started :param name: Name of service to get or write to config :return: Onion object, that is connected and service is started @@ -23,16 +23,20 @@ def integrate_onion(port: int, name: str) -> Onion: if not onion.connected_to_tor: onion.cleanup() sys.exit(1) - - print(f'[green]Available on[/] ' - f'{(onion_host := onion.start_onion_service(name))}.onion') - + + print( + f"[green]Available on[/] " + f"{(onion_host := onion.start_onion_service(name))}.onion" + ) + auth = AuthFile(name) - auth['host'] = f'{onion_host}.onion' - auth['auth'] = onion.auth_string - print(f'To connect to server just share [green]{auth.filename}[/] file') - print(f'Or use [#ff901b]service id[/] and [#564ec3]auth string[/]: \n' - f'[#ff901b]{onion_host}[/] \n' - f'[#564ec3]{services[name].client_auth_priv_key}[/]') - + auth["host"] = f"{onion_host}.onion" + auth["auth"] = onion.auth_string + print(f"To connect to server just share [green]{auth.filename}[/] file") + print( + f"Or use [#ff901b]service id[/] and [#564ec3]auth string[/]: \n" + f"[#ff901b]{onion_host}[/] \n" + f"[#564ec3]{services[name].client_auth_priv_key}[/]" + ) + return onion diff --git a/dragonion_server/modules/server/routes.py b/dragonion_server/modules/server/routes.py index 59e37b3..f93cb74 100644 --- a/dragonion_server/modules/server/routes.py +++ b/dragonion_server/modules/server/routes.py @@ -1,6 +1,6 @@ from fastapi import APIRouter, WebSocket -from .handlers.websocket_server import serve_websocket +from .handlers.websocket_server import serve_websocket router = APIRouter() diff --git a/dragonion_server/modules/server/server.py b/dragonion_server/modules/server/server.py index ac719b9..22e99d4 100644 --- a/dragonion_server/modules/server/server.py +++ b/dragonion_server/modules/server/server.py @@ -1,6 +1,8 @@ -from fastapi import FastAPI import uvicorn +from fastapi import FastAPI + from dragonion_server.utils.onion import get_available_port + from .integration import integrate_onion from .routes import router @@ -8,15 +10,15 @@ from .routes import router def get_app(port: int, name: str) -> FastAPI: """ Creates FastAPI object and runs integrate_onion - :param port: Must be same with port on which uvicorn is running + :param port: Must be same with port on which uvicorn is running :param name: Name of service :return: FastAPI object with onion.cleanup function on shutdown """ onion = integrate_onion(port, name) return FastAPI( - title=f'dragonion-server: {name}', - description=f'Secure dragonion chat endpoint server - service {name}', - on_shutdown=[onion.cleanup] + title=f"dragonion-server: {name}", + description=f"Secure dragonion chat endpoint server - service {name}", + on_shutdown=[onion.cleanup], ) @@ -25,22 +27,22 @@ def run(name: str, port: int | None = get_available_port()): Runs service with specified name and starts onion :param name: Name of service :param port: Port where to start service, if not specified - gets random available - :return: + :return: """ if port is None: port = get_available_port() app = get_app(port, name) app.include_router(router) - uvicorn.run(app, host='0.0.0.0', port=port) + uvicorn.run(app, host="0.0.0.0", port=port) def run_without_onion(name: str, port: int | None = get_available_port()): if port is None: port = get_available_port() - + app = FastAPI( - title=f'dragonion-server: {name}', - description=f'Secure dragonion chat endpoint server - service {name}' + title=f"dragonion-server: {name}", + description=f"Secure dragonion chat endpoint server - service {name}", ) app.include_router(router) - uvicorn.run(app, host='0.0.0.0', port=port) + uvicorn.run(app, host="0.0.0.0", port=port) diff --git a/dragonion_server/utils/config/__init__.py b/dragonion_server/utils/config/__init__.py index d679cb6..1cb7673 100644 --- a/dragonion_server/utils/config/__init__.py +++ b/dragonion_server/utils/config/__init__.py @@ -1,6 +1,3 @@ from . import db, models -__all__ = [ - 'db', - 'models' -] +__all__ = ["db", "models"] diff --git a/dragonion_server/utils/config/db.py b/dragonion_server/utils/config/db.py index 56efe7a..b36e7d4 100644 --- a/dragonion_server/utils/config/db.py +++ b/dragonion_server/utils/config/db.py @@ -3,12 +3,8 @@ import sqlitedict class ConfigDatabase(sqlitedict.SqliteDict): def __init__(self, tablename): - super().__init__( - filename='data.storage', - tablename=tablename, - autocommit=True - ) + super().__init__(filename="data.storage", tablename=tablename, autocommit=True) -config = ConfigDatabase('config') -services = ConfigDatabase('services') +config = ConfigDatabase("config") +services = ConfigDatabase("services") diff --git a/dragonion_server/utils/config/models.py b/dragonion_server/utils/config/models.py index 28e5814..d99b351 100644 --- a/dragonion_server/utils/config/models.py +++ b/dragonion_server/utils/config/models.py @@ -13,5 +13,5 @@ class ServiceModel: client_auth_pub_key: str service_id: str = None - key_content: str = 'ED25519-V3' - key_type: str = 'NEW' + key_content: str = "ED25519-V3" + key_type: str = "NEW" diff --git a/dragonion_server/utils/core/dirs.py b/dragonion_server/utils/core/dirs.py index dba8985..c4a39d1 100644 --- a/dragonion_server/utils/core/dirs.py +++ b/dragonion_server/utils/core/dirs.py @@ -5,28 +5,33 @@ import sys def get_resource_path(filename): - application_path = 'resources' + application_path = "resources" return os.path.join(application_path, filename) def get_tor_paths(): - if (platform.system() != "Darwin" and - platform.machine().lower() in ['aarch64', 'arm64']): - if shutil.which('tor'): - return 'tor' + if platform.system() != "Darwin" and platform.machine().lower() in [ + "aarch64", + "arm64", + ]: + if shutil.which("tor"): + return "tor" else: - print('Detected ARM system and tor is not installed or added to PATH. ' - 'Please, consider reading documentation and installing application ' - 'properly') + print( + "Detected ARM system and tor is not installed or added to PATH. " + "Please, consider reading documentation and installing application " + "properly" + ) sys.exit(1) - + else: from ..onion.tor_downloader import download_tor + if platform.system() in ["Linux", "Darwin"]: - tor_path = os.path.join(build_data_dir(), 'tor/tor') + tor_path = os.path.join(build_data_dir(), "tor/tor") elif platform.system() == "Windows": - tor_path = os.path.join(build_data_dir(), 'tor/tor.exe') + tor_path = os.path.join(build_data_dir(), "tor/tor.exe") else: raise Exception("Platform not supported") @@ -37,7 +42,7 @@ def get_tor_paths(): def build_data_dir(): - dragonion_data_dir = 'data' + dragonion_data_dir = "data" os.makedirs(dragonion_data_dir, exist_ok=True) return dragonion_data_dir diff --git a/dragonion_server/utils/onion/__init__.py b/dragonion_server/utils/onion/__init__.py index d84c44a..d03e6c2 100644 --- a/dragonion_server/utils/onion/__init__.py +++ b/dragonion_server/utils/onion/__init__.py @@ -1,7 +1,3 @@ -from .onion import Onion -from .onion import get_available_port +from .onion import Onion, get_available_port -__all__ = [ - 'Onion', - 'get_available_port' -] +__all__ = ["Onion", "get_available_port"] diff --git a/dragonion_server/utils/onion/onion.py b/dragonion_server/utils/onion/onion.py index 60a0a18..e2104ec 100644 --- a/dragonion_server/utils/onion/onion.py +++ b/dragonion_server/utils/onion/onion.py @@ -1,23 +1,23 @@ -from stem.control import Controller -from .stem_process import launch_tor_with_config -from stem import ProtocolError - -import socket -import random +import base64 import os -import psutil +import platform +import random +import socket import subprocess import tempfile -import platform import time -import base64 + import nacl.public - +import psutil from rich import print +from stem import ProtocolError +from stem.control import Controller -from dragonion_server.utils.core import dirs from dragonion_server.utils import config from dragonion_server.utils.config.db import services +from dragonion_server.utils.core import dirs + +from .stem_process import launch_tor_with_config def get_available_port(min_port: int = 1000, max_port: int = 65535): @@ -61,9 +61,9 @@ class Onion(object): try: cmdline = proc.cmdline() if ( - cmdline[0] == self.tor_path - and cmdline[1] == "-f" - and cmdline[2] == self.tor_torrc + cmdline[0] == self.tor_path + and cmdline[1] == "-f" + and cmdline[2] == self.tor_torrc ): proc.terminate() proc.wait() @@ -82,18 +82,18 @@ class Onion(object): self.kill_same_tor() tor_config = { - 'DataDirectory': tor_data_directory_name, - 'SocksPort': str(self.tor_socks_port), - 'CookieAuthentication': '1', - 'CookieAuthFile': self.tor_cookie_auth_file, - 'AvoidDiskWrites': '1', - 'Log': [ - 'NOTICE stdout' - ] + "DataDirectory": tor_data_directory_name, + "SocksPort": str(self.tor_socks_port), + "CookieAuthentication": "1", + "CookieAuthFile": self.tor_cookie_auth_file, + "AvoidDiskWrites": "1", + "Log": ["NOTICE stdout"], } - if platform.system() in ["Windows", "Darwin"] or \ - len(tor_data_directory_name) > 90: + if ( + platform.system() in ["Windows", "Darwin"] + or len(tor_data_directory_name) > 90 + ): try: self.tor_control_port = get_available_port(1000, 65535) tor_config = tor_config | {"ControlPort": str(self.tor_control_port)} @@ -102,24 +102,22 @@ class Onion(object): self.tor_control_socket = None else: self.tor_control_port = None - self.tor_control_socket = os.path.abspath(os.path.join( - tor_data_directory_name, "control_socket" - )) + self.tor_control_socket = os.path.abspath( + os.path.join(tor_data_directory_name, "control_socket") + ) tor_config = tor_config | {"ControlSocket": str(self.tor_control_socket)} return tor_config - + def connect(self): - self.tor_data_directory = tempfile.TemporaryDirectory( - dir=dirs.build_tmp_dir() - ) + self.tor_data_directory = tempfile.TemporaryDirectory(dir=dirs.build_tmp_dir()) self.tor_data_directory_name = self.tor_data_directory.name self.tor_proc = launch_tor_with_config( config=self.get_config(self.tor_data_directory_name), tor_cmd=self.tor_path, take_ownership=True, - init_msg_handler=print + init_msg_handler=print, ) time.sleep(2) @@ -149,14 +147,12 @@ class Onion(object): client_auth_priv_key_raw = nacl.public.PrivateKey.generate() client_auth_priv_key = key_str(client_auth_priv_key_raw) - client_auth_pub_key = key_str( - client_auth_priv_key_raw.public_key - ) + client_auth_pub_key = key_str(client_auth_priv_key_raw.public_key) services[name] = config.models.ServiceModel( port=port, client_auth_priv_key=client_auth_priv_key, - client_auth_pub_key=client_auth_pub_key + client_auth_pub_key=client_auth_pub_key, ) return services[name] @@ -167,7 +163,7 @@ class Onion(object): :return: .onion url """ if name not in services.keys(): - raise 'Service not created' + raise "Service not created" service: config.models.ServiceModel = services[name] @@ -193,8 +189,9 @@ class Onion(object): service.key_type = "ED25519-V3" service.key_content = res.private_key - self.auth_string = f'{res.service_id}:descriptor:' \ - f'x25519:{service.client_auth_priv_key}' + self.auth_string = ( + f"{res.service_id}:descriptor:" f"x25519:{service.client_auth_priv_key}" + ) services[name] = service @@ -204,9 +201,7 @@ class Onion(object): service: config.models.ServiceModel = services[name] if service.service_id: try: - self.c.remove_ephemeral_hidden_service( - service.service_id - ) + self.c.remove_ephemeral_hidden_service(service.service_id) except Exception as e: print(e) @@ -222,8 +217,8 @@ class Onion(object): rendezvous_circuit_ids = [] for c in self.c.get_circuits(): if ( - c.purpose == "HS_SERVICE_REND" - and c.rend_query in self.graceful_close_onions + c.purpose == "HS_SERVICE_REND" + and c.rend_query in self.graceful_close_onions ): rendezvous_circuit_ids.append(c.id) @@ -237,9 +232,7 @@ class Onion(object): num_rend_circuits += 1 if num_rend_circuits == 0: - print( - "\rTor rendezvous circuits have closed" + " " * 20 - ) + print("\rTor rendezvous circuits have closed" + " " * 20) break if num_rend_circuits == 1: @@ -271,7 +264,7 @@ class Onion(object): try: self.tor_data_directory.cleanup() except Exception as e: - print(f'Cannot cleanup temporary directory: {e}') + print(f"Cannot cleanup temporary directory: {e}") @property def get_tor_socks_port(self): diff --git a/dragonion_server/utils/onion/stem_process.py b/dragonion_server/utils/onion/stem_process.py index d08b86f..1b6c871 100644 --- a/dragonion_server/utils/onion/stem_process.py +++ b/dragonion_server/utils/onion/stem_process.py @@ -15,13 +15,21 @@ import stem.util.str_tools import stem.util.system import stem.version -NO_TORRC = '' +NO_TORRC = "" DEFAULT_INIT_TIMEOUT = 90 -def launch_tor(tor_cmd='tor', args=None, torrc_path=None, completion_percent=100, - init_msg_handler=None, timeout=DEFAULT_INIT_TIMEOUT, - take_ownership=False, close_output=True, stdin=None): +def launch_tor( + tor_cmd="tor", + args=None, + torrc_path=None, + completion_percent=100, + init_msg_handler=None, + timeout=DEFAULT_INIT_TIMEOUT, + take_ownership=False, + close_output=True, + stdin=None, +): """ Initializes a tor process. This blocks until initialization completes or we error out. @@ -68,13 +76,14 @@ def launch_tor(tor_cmd='tor', args=None, torrc_path=None, completion_percent=100 if stem.util.system.is_windows(): if timeout is not None and timeout != DEFAULT_INIT_TIMEOUT: - raise OSError('You cannot launch tor with a timeout on Windows') + raise OSError("You cannot launch tor with a timeout on Windows") timeout = None - elif threading.current_thread().__class__.__name__ != '_MainThread': + elif threading.current_thread().__class__.__name__ != "_MainThread": if timeout is not None and timeout != DEFAULT_INIT_TIMEOUT: raise OSError( - 'Launching tor with a timeout can only be done in the main thread') + "Launching tor with a timeout can only be done in the main thread" + ) timeout = None @@ -88,8 +97,10 @@ def launch_tor(tor_cmd='tor', args=None, torrc_path=None, completion_percent=100 elif not os.path.isfile(tor_cmd): raise OSError("'%s' doesn't exist" % tor_cmd) elif not stem.util.system.is_available(tor_cmd): - raise OSError(f"{tor_cmd} isn't available on your system. " - f"Maybe it's not in your PATH?") + raise OSError( + f"{tor_cmd} isn't available on your system. " + f"Maybe it's not in your PATH?" + ) # double check that we have a torrc to work with if torrc_path not in (None, NO_TORRC) and not os.path.exists(torrc_path): @@ -103,13 +114,13 @@ def launch_tor(tor_cmd='tor', args=None, torrc_path=None, completion_percent=100 if torrc_path: if torrc_path == NO_TORRC: - temp_file = tempfile.mkstemp(prefix='empty-torrc-', text=True)[1] - runtime_args += ['-f', temp_file] + temp_file = tempfile.mkstemp(prefix="empty-torrc-", text=True)[1] + runtime_args += ["-f", temp_file] else: - runtime_args += ['-f', torrc_path] + runtime_args += ["-f", torrc_path] if take_ownership: - runtime_args += ['__OwningControllerProcess', str(os.getpid())] + runtime_args += ["__OwningControllerProcess", str(os.getpid())] tor_process = None @@ -119,8 +130,9 @@ def launch_tor(tor_cmd='tor', args=None, torrc_path=None, completion_percent=100 stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE, - env=None if platform.system() == 'Windows' else - {"LD_LIBRARY_PATH": os.path.dirname(tor_cmd)} + env=None + if platform.system() == "Windows" + else {"LD_LIBRARY_PATH": os.path.dirname(tor_cmd)}, ) if stdin: @@ -129,15 +141,16 @@ def launch_tor(tor_cmd='tor', args=None, torrc_path=None, completion_percent=100 tor_process.stdin.close() if timeout: + def timeout_handler(*_): - raise OSError('reached a %i second timeout without success' % timeout) + raise OSError("reached a %i second timeout without success" % timeout) signal.signal(signal.SIGALRM, timeout_handler) signal.setitimer(signal.ITIMER_REAL, timeout) - bootstrap_line = re.compile('Bootstrapped ([0-9]+)%') - problem_line = re.compile('\\[(warn|err)] (.*)$') - last_problem = 'Timed out' + bootstrap_line = re.compile("Bootstrapped ([0-9]+)%") + problem_line = re.compile("\\[(warn|err)] (.*)$") + last_problem = "Timed out" while True: # Tor's stdout will be read as ASCII bytes. This is fine for python 2, but @@ -147,12 +160,12 @@ def launch_tor(tor_cmd='tor', args=None, torrc_path=None, completion_percent=100 # It seems like python 2.x is perfectly happy for this to be unicode, so # normalizing to that. - init_line = tor_process.stdout.readline().decode('utf-8', 'replace').strip() + init_line = tor_process.stdout.readline().decode("utf-8", "replace").strip() # this will provide empty results if the process is terminated if not init_line: - raise OSError('Process terminated: %s' % last_problem) + raise OSError("Process terminated: %s" % last_problem) # provide the caller with the initialization message if they want it @@ -169,9 +182,9 @@ def launch_tor(tor_cmd='tor', args=None, torrc_path=None, completion_percent=100 elif problem_match: runlevel, msg = problem_match.groups() - if 'see warnings above' not in msg: - if ': ' in msg: - msg = msg.split(': ')[-1].strip() + if "see warnings above" not in msg: + if ": " in msg: + msg = msg.split(": ")[-1].strip() last_problem = msg except Exception as e: @@ -199,9 +212,15 @@ def launch_tor(tor_cmd='tor', args=None, torrc_path=None, completion_percent=100 assert e -def launch_tor_with_config(config, tor_cmd='tor', completion_percent=100, - init_msg_handler=None, timeout=DEFAULT_INIT_TIMEOUT, - take_ownership=False, close_output=True): +def launch_tor_with_config( + config, + tor_cmd="tor", + completion_percent=100, + init_msg_handler=None, + timeout=DEFAULT_INIT_TIMEOUT, + take_ownership=False, + close_output=True, +): """ Initializes a tor process, like :func:`~stem.process.launch_tor`, but with a customized configuration. This writes a temporary torrc to disk, launches @@ -246,62 +265,71 @@ def launch_tor_with_config(config, tor_cmd='tor', completion_percent=100, """ try: - use_stdin = stem.version.get_system_tor_version( - tor_cmd) >= stem.version.Requirement.TORRC_VIA_STDIN + use_stdin = ( + stem.version.get_system_tor_version(tor_cmd) + >= stem.version.Requirement.TORRC_VIA_STDIN + ) except IOError: use_stdin = False # we need to be sure that we're logging to stdout to figure out when we're # done bootstrapping - if 'Log' in config: - stdout_options = ['DEBUG stdout', 'INFO stdout', 'NOTICE stdout'] + if "Log" in config: + stdout_options = ["DEBUG stdout", "INFO stdout", "NOTICE stdout"] - if isinstance(config['Log'], str): - config['Log'] = [config['Log']] + if isinstance(config["Log"], str): + config["Log"] = [config["Log"]] has_stdout = False - for log_config in config['Log']: + for log_config in config["Log"]: if log_config in stdout_options: has_stdout = True break if not has_stdout: - config['Log'].append('NOTICE stdout') + config["Log"].append("NOTICE stdout") - config_str = '' + config_str = "" for key, values in list(config.items()): if isinstance(values, str): - config_str += '%s %s\n' % (key, values) + config_str += "%s %s\n" % (key, values) else: for value in values: - config_str += '%s %s\n' % (key, value) + config_str += "%s %s\n" % (key, value) if use_stdin: return launch_tor( tor_cmd=tor_cmd, - args=['-f', '-'], + args=["-f", "-"], completion_percent=completion_percent, init_msg_handler=init_msg_handler, timeout=timeout, take_ownership=take_ownership, close_output=close_output, - stdin=config_str + stdin=config_str, ) else: - torrc_descriptor, torrc_path = tempfile.mkstemp(prefix='torrc-', text=True) + torrc_descriptor, torrc_path = tempfile.mkstemp(prefix="torrc-", text=True) try: - with open(torrc_path, 'w') as torrc_file: + with open(torrc_path, "w") as torrc_file: torrc_file.write(config_str) # prevents tor from error-ing out due to a missing torrc if it gets a sighup - args = ['__ReloadTorrcOnSIGHUP', '0'] + args = ["__ReloadTorrcOnSIGHUP", "0"] - return launch_tor(tor_cmd, args, torrc_path, completion_percent, - init_msg_handler, timeout, take_ownership) + return launch_tor( + tor_cmd, + args, + torrc_path, + completion_percent, + init_msg_handler, + timeout, + take_ownership, + ) finally: try: os.close(torrc_descriptor) diff --git a/dragonion_server/utils/onion/tor_downloader.py b/dragonion_server/utils/onion/tor_downloader.py index 7b16e53..b9a50ec 100644 --- a/dragonion_server/utils/onion/tor_downloader.py +++ b/dragonion_server/utils/onion/tor_downloader.py @@ -1,51 +1,51 @@ -import os import io -import tarfile -import requests +import os import re import sys +import tarfile from typing import Literal +import requests + def get_latest_version() -> str: """ Gets latest non-alfa version name from dist.torproject.org :return: """ - r = requests.get('https://dist.torproject.org/torbrowser/').text + r = requests.get("https://dist.torproject.org/torbrowser/").text results = re.findall(r'(.+)/', r) for res in results: - if 'a' not in res: + if "a" not in res: return res def get_build() -> Literal[ - 'windows-x86_64', - 'linux-x86_64', - 'macos-x86_64', - 'macos-aarch64' + "windows-x86_64", "linux-x86_64", "macos-x86_64", "macos-aarch64" ]: """ Gets proper build name for your system :return: """ - if sys.platform == 'win32': - return 'windows-x86_64' - elif sys.platform == 'linux': - return 'linux-x86_64' - elif sys.platform == 'darwin': + if sys.platform == "win32": + return "windows-x86_64" + elif sys.platform == "linux": + return "linux-x86_64" + elif sys.platform == "darwin": import platform - if platform.uname().machine == 'arm64': - return 'macos-aarch64' + + if platform.uname().machine == "arm64": + return "macos-aarch64" else: - return 'macos-x86_64' + return "macos-x86_64" else: - raise 'System not supported' + raise "System not supported" -def get_tor_expert_bundles(version: str = get_latest_version(), - platform: str = get_build()): +def get_tor_expert_bundles( + version: str = get_latest_version(), platform: str = get_build() +): """ Returns a link for downloading tor expert bundle by version and platform :param version: Tor expert bundle version that exists in dist.torproject.org @@ -53,11 +53,13 @@ def get_tor_expert_bundles(version: str = get_latest_version(), get_build() :return: """ - return f'https://dist.torproject.org/torbrowser/{version}/tor-expert-bundle-' \ - f'{version}-{platform}.tar.gz' + return ( + f"https://dist.torproject.org/torbrowser/{version}/tor-expert-bundle-" + f"{version}-{platform}.tar.gz" + ) -def download_tor(url: str = get_tor_expert_bundles(), dist: str = 'tor'): +def download_tor(url: str = get_tor_expert_bundles(), dist: str = "tor"): """ Downloads tor from url and unpacks it to specified directory. Note, that it doesn't unpack only tor executable to dist folder, but creates there @@ -69,15 +71,15 @@ def download_tor(url: str = get_tor_expert_bundles(), dist: str = 'tor'): if not os.path.exists(dist): os.makedirs(dist) - (tar := tarfile.open(fileobj=io.BytesIO(requests.get(url).content), - mode='r:gz')).extractall( + ( + tar := tarfile.open(fileobj=io.BytesIO(requests.get(url).content), mode="r:gz") + ).extractall( members=[ - tarinfo - for tarinfo - in tar.getmembers() - if tarinfo.name.startswith("tor/") - ], path=dist) + tarinfo for tarinfo in tar.getmembers() if tarinfo.name.startswith("tor/") + ], + path=dist, + ) -if __name__ == '__main__': +if __name__ == "__main__": download_tor()