Autoformat
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
from dragonion_server import main
|
from dragonion_server import main
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
from .common import cli
|
from .common import cli
|
||||||
|
|
||||||
|
__all__ = ["cli"]
|
||||||
__all__ = [
|
|
||||||
'cli'
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -2,34 +2,34 @@ import os
|
|||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
from dragonion_server.utils.config import db
|
|
||||||
from dragonion_server.common import console
|
from dragonion_server.common import console
|
||||||
|
from dragonion_server.utils.config import db
|
||||||
|
|
||||||
|
|
||||||
class ServiceRemoveCommand(click.Command):
|
class ServiceRemoveCommand(click.Command):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name='remove',
|
name="remove",
|
||||||
callback=self.callback,
|
callback=self.callback,
|
||||||
params=[
|
params=[
|
||||||
click.Option(
|
click.Option(
|
||||||
('--name', '-n'),
|
("--name", "-n"),
|
||||||
required=True,
|
required=True,
|
||||||
prompt=True,
|
prompt=True,
|
||||||
type=str,
|
type=str,
|
||||||
help='Name of service to write to'
|
help="Name of service to write to",
|
||||||
)
|
)
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def callback(name: str):
|
def callback(name: str):
|
||||||
try:
|
try:
|
||||||
del db.services[name]
|
del db.services[name]
|
||||||
if os.path.isfile(f'{name}.auth'):
|
if os.path.isfile(f"{name}.auth"):
|
||||||
os.remove(f'{name}.auth')
|
os.remove(f"{name}.auth")
|
||||||
|
|
||||||
print(f'Removed service {name}')
|
print(f"Removed service {name}")
|
||||||
except KeyError:
|
except KeyError:
|
||||||
print(f'Service "{name}" does not exist in this storage')
|
print(f'Service "{name}" does not exist in this storage')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -1,60 +1,63 @@
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
from dragonion_server.modules.server import run, run_without_onion, integrate_onion
|
|
||||||
from dragonion_server.common import console
|
from dragonion_server.common import console
|
||||||
|
from dragonion_server.modules.server import integrate_onion, run, run_without_onion
|
||||||
|
|
||||||
|
|
||||||
class ServiceRunCommand(click.Command):
|
class ServiceRunCommand(click.Command):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name='run',
|
name="run",
|
||||||
callback=self.callback,
|
callback=self.callback,
|
||||||
params=[
|
params=[
|
||||||
click.Option(
|
click.Option(
|
||||||
('--name', '-n'),
|
("--name", "-n"),
|
||||||
required=True,
|
required=True,
|
||||||
prompt=True,
|
prompt=True,
|
||||||
type=str,
|
type=str,
|
||||||
help='Name of service to write to'
|
help="Name of service to write to",
|
||||||
),
|
),
|
||||||
click.Option(
|
click.Option(
|
||||||
('--port', '-p'),
|
("--port", "-p"),
|
||||||
required=False,
|
required=False,
|
||||||
prompt=True,
|
prompt=True,
|
||||||
prompt_required=False,
|
prompt_required=False,
|
||||||
type=int,
|
type=int,
|
||||||
help='Port to start service on'
|
help="Port to start service on",
|
||||||
),
|
),
|
||||||
click.Option(
|
click.Option(
|
||||||
('--without-tor', '-wt'),
|
("--without-tor", "-wt"),
|
||||||
is_flag=True,
|
is_flag=True,
|
||||||
help='Run service without tor'
|
help="Run service without tor",
|
||||||
),
|
),
|
||||||
click.Option(
|
click.Option(
|
||||||
('--only-tor', '-ot'),
|
("--only-tor", "-ot"),
|
||||||
is_flag=True,
|
is_flag=True,
|
||||||
help='Run only tor proxy to service'
|
help="Run only tor proxy to service",
|
||||||
)
|
),
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def callback(name: str, port: int | None, without_tor: bool, only_tor: bool):
|
def callback(name: str, port: int | None, without_tor: bool, only_tor: bool):
|
||||||
try:
|
try:
|
||||||
if without_tor and only_tor:
|
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)
|
sys.exit(1)
|
||||||
elif without_tor:
|
elif without_tor:
|
||||||
run_without_onion(name, port)
|
run_without_onion(name, port)
|
||||||
elif only_tor:
|
elif only_tor:
|
||||||
if port is None:
|
if port is None:
|
||||||
print('For this mode, you need to specify port, '
|
print(
|
||||||
'to which requests will be redirected. Cannot start '
|
"For this mode, you need to specify port, "
|
||||||
'tor service, exiting')
|
"to which requests will be redirected. Cannot start "
|
||||||
|
"tor service, exiting"
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
onion = integrate_onion(port, name)
|
onion = integrate_onion(port, name)
|
||||||
input('Press Enter to stop onion and service...')
|
input("Press Enter to stop onion and service...")
|
||||||
onion.cleanup()
|
onion.cleanup()
|
||||||
else:
|
else:
|
||||||
run(name, port)
|
run(name, port)
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
from ...utils import ModuleGroup
|
from ...utils import ModuleGroup
|
||||||
from .write import ServiceWriteCommand
|
|
||||||
from .run import ServiceRunCommand
|
|
||||||
from .remove import ServiceRemoveCommand
|
from .remove import ServiceRemoveCommand
|
||||||
|
from .run import ServiceRunCommand
|
||||||
|
from .write import ServiceWriteCommand
|
||||||
|
|
||||||
service_group = ModuleGroup(
|
service_group = ModuleGroup(
|
||||||
name='service',
|
name="service",
|
||||||
commands={
|
commands={
|
||||||
'write': ServiceWriteCommand(),
|
"write": ServiceWriteCommand(),
|
||||||
'run': ServiceRunCommand(),
|
"run": ServiceRunCommand(),
|
||||||
'remove': ServiceRemoveCommand()
|
"remove": ServiceRemoveCommand(),
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
import click
|
import click
|
||||||
|
|
||||||
from dragonion_server.utils.onion import Onion
|
|
||||||
from dragonion_server.common import console
|
from dragonion_server.common import console
|
||||||
|
from dragonion_server.utils.onion import Onion
|
||||||
|
|
||||||
|
|
||||||
class ServiceWriteCommand(click.Command):
|
class ServiceWriteCommand(click.Command):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name='write',
|
name="write",
|
||||||
callback=self.callback,
|
callback=self.callback,
|
||||||
params=[
|
params=[
|
||||||
click.Option(
|
click.Option(
|
||||||
('--name', '-n'),
|
("--name", "-n"),
|
||||||
required=True,
|
required=True,
|
||||||
prompt=True,
|
prompt=True,
|
||||||
type=str,
|
type=str,
|
||||||
help='Name of service to write to'
|
help="Name of service to write to",
|
||||||
),
|
),
|
||||||
click.Option(
|
click.Option(
|
||||||
('--port', '-p'),
|
("--port", "-p"),
|
||||||
required=True,
|
required=True,
|
||||||
prompt=True,
|
prompt=True,
|
||||||
type=int,
|
type=int,
|
||||||
help='Port to start service on'
|
help="Port to start service on",
|
||||||
)
|
),
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
import click
|
import click
|
||||||
|
|
||||||
from .cmd.service.service import service_group
|
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()
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
from .groups import ModuleGroup
|
from .groups import ModuleGroup
|
||||||
|
|
||||||
|
__all__ = ["ModuleGroup"]
|
||||||
__all__ = [
|
|
||||||
'ModuleGroup'
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import click
|
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
|
import click
|
||||||
|
|
||||||
|
|
||||||
class ModuleGroup(click.Group):
|
class ModuleGroup(click.Group):
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -13,6 +14,6 @@ class ModuleGroup(click.Group):
|
|||||||
) -> None:
|
) -> None:
|
||||||
new_commands = dict()
|
new_commands = dict()
|
||||||
for command_key in commands.keys():
|
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)
|
super().__init__(name, new_commands, **attrs)
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
from .server import run, run_without_onion
|
|
||||||
from .integration import integrate_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'
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
from attrs import define
|
from attrs import define
|
||||||
from fastapi import WebSocket
|
|
||||||
from dragonion_core.proto.web.webmessage import (
|
from dragonion_core.proto.web.webmessage import (
|
||||||
|
WebErrorMessage,
|
||||||
set_time,
|
set_time,
|
||||||
webmessages_union,
|
|
||||||
webmessage_error_message_literal,
|
webmessage_error_message_literal,
|
||||||
WebErrorMessage
|
webmessages_union,
|
||||||
)
|
)
|
||||||
|
from fastapi import WebSocket
|
||||||
|
|
||||||
|
|
||||||
@define
|
@define
|
||||||
@@ -23,17 +23,10 @@ class Connection(object):
|
|||||||
"""
|
"""
|
||||||
await self.ws.send_text(set_time(obj).to_json())
|
await self.ws.send_text(set_time(obj).to_json())
|
||||||
|
|
||||||
async def send_error(
|
async def send_error(self, error_message: webmessage_error_message_literal):
|
||||||
self,
|
|
||||||
error_message: webmessage_error_message_literal
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Sends error with specified messages
|
Sends error with specified messages
|
||||||
:param error_message: See webmessage_error_message_literal for available
|
:param error_message: See webmessage_error_message_literal for available
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
await self.send_webmessage(
|
await self.send_webmessage(WebErrorMessage(error_message=error_message))
|
||||||
WebErrorMessage(
|
|
||||||
error_message=error_message
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,25 +1,24 @@
|
|||||||
from attrs import define
|
from datetime import datetime
|
||||||
from .connection import Connection
|
from json.decoder import JSONDecodeError
|
||||||
from .exceptions import GotInvalidWebmessage
|
|
||||||
from typing import Dict
|
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 fastapi import WebSocket
|
||||||
|
|
||||||
from json.decoder import JSONDecodeError
|
from .connection import Connection
|
||||||
|
from .exceptions import GotInvalidWebmessage
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
@define
|
@define
|
||||||
@@ -33,50 +32,53 @@ class Room(object):
|
|||||||
:param ws: Websocket of connection
|
:param ws: Websocket of connection
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
print('Incoming connection')
|
print("Incoming connection")
|
||||||
await ws.accept()
|
await ws.accept()
|
||||||
try:
|
try:
|
||||||
connection_message = WebConnectionMessage.from_json(
|
connection_message = WebConnectionMessage.from_json(await ws.receive_text())
|
||||||
await ws.receive_text()
|
|
||||||
)
|
|
||||||
except JSONDecodeError:
|
except JSONDecodeError:
|
||||||
await ws.send_text(set_time(WebErrorMessage(
|
await ws.send_text(
|
||||||
'invalid_webmessage'
|
set_time(WebErrorMessage("invalid_webmessage")).to_json()
|
||||||
)).to_json())
|
)
|
||||||
await ws.close(reason='invalid_webmessage')
|
await ws.close(reason="invalid_webmessage")
|
||||||
return
|
return
|
||||||
|
|
||||||
connection = Connection(
|
connection = Connection(
|
||||||
username=connection_message.username,
|
username=connection_message.username,
|
||||||
ws=ws,
|
ws=ws,
|
||||||
public_key=connection_message.public_key,
|
public_key=connection_message.public_key,
|
||||||
password=connection_message.password
|
password=connection_message.password,
|
||||||
)
|
)
|
||||||
|
|
||||||
if connection_message.username in self.connections.keys():
|
if connection_message.username in self.connections.keys():
|
||||||
await connection.send_error(
|
await connection.send_error("username_exists")
|
||||||
'username_exists'
|
await ws.close(reason="username_exists")
|
||||||
)
|
|
||||||
await ws.close(reason='username_exists')
|
|
||||||
return
|
return
|
||||||
|
|
||||||
self.connections[connection_message.username] = connection
|
self.connections[connection_message.username] = connection
|
||||||
await connection.send_webmessage(WebConnectionResultMessage(
|
await connection.send_webmessage(
|
||||||
|
WebConnectionResultMessage(
|
||||||
connected_users=dict(
|
connected_users=dict(
|
||||||
map(
|
map(
|
||||||
lambda i, j: (i, j),
|
lambda i, j: (i, j),
|
||||||
[_username for _username in list(self.connections.keys())
|
[
|
||||||
if self.connections[_username].password ==
|
_username
|
||||||
connection_message.password],
|
for _username in list(self.connections.keys())
|
||||||
[_connection.public_key for _connection
|
if self.connections[_username].password
|
||||||
in self.connections.values() if _connection.password ==
|
== connection_message.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)
|
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
|
return connection
|
||||||
|
|
||||||
async def broadcast_webmessage(self, obj: webmessages_union):
|
async def broadcast_webmessage(self, obj: webmessages_union):
|
||||||
@@ -112,26 +114,15 @@ class Room(object):
|
|||||||
:param message: Content
|
:param message: Content
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
await self.broadcast_webmessage(
|
await self.broadcast_webmessage(WebNotificationMessage(message=message))
|
||||||
WebNotificationMessage(
|
|
||||||
message=message
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
async def broadcast_error(
|
async def broadcast_error(self, error_message: webmessage_error_message_literal):
|
||||||
self,
|
|
||||||
error_message: webmessage_error_message_literal
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Broadcasts server error
|
Broadcasts server error
|
||||||
:param error_message: See webmessage_error_message_literal
|
:param error_message: See webmessage_error_message_literal
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
await self.broadcast_webmessage(
|
await self.broadcast_webmessage(WebErrorMessage(error_message=error_message))
|
||||||
WebErrorMessage(
|
|
||||||
error_message=error_message
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
async def broadcast_user_disconnected(self, username: str):
|
async def broadcast_user_disconnected(self, username: str):
|
||||||
"""
|
"""
|
||||||
@@ -139,11 +130,7 @@ class Room(object):
|
|||||||
:param username: Username of user that disconnected
|
:param username: Username of user that disconnected
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
await self.broadcast_webmessage(
|
await self.broadcast_webmessage(WebDisconnectMessage(username=username))
|
||||||
WebDisconnectMessage(
|
|
||||||
username=username
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
async def get_connection_by(self, attribute: str, value: str) -> Connection | None:
|
async def get_connection_by(self, attribute: str, value: str) -> Connection | None:
|
||||||
"""
|
"""
|
||||||
@@ -170,9 +157,7 @@ class Room(object):
|
|||||||
del self.connections[connection.username]
|
del self.connections[connection.username]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await connection.ws.close(
|
await connection.ws.close(reason=close_reason)
|
||||||
reason=close_reason
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
assert e
|
assert e
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
from .connection import Connection
|
|
||||||
from .room import Room
|
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from dragonion_core.proto.web.webmessage import (
|
from dragonion_core.proto.web.webmessage import webmessage_error_message_literal
|
||||||
webmessage_error_message_literal
|
|
||||||
)
|
from .connection import Connection
|
||||||
|
from .room import Room
|
||||||
|
|
||||||
|
|
||||||
class Service(object):
|
class Service(object):
|
||||||
@@ -21,10 +20,7 @@ class Service(object):
|
|||||||
for room in self.rooms.values():
|
for room in self.rooms.values():
|
||||||
await room.broadcast_notification(message)
|
await room.broadcast_notification(message)
|
||||||
|
|
||||||
async def broadcast_error(
|
async def broadcast_error(self, error_message: webmessage_error_message_literal):
|
||||||
self,
|
|
||||||
error_message: webmessage_error_message_literal
|
|
||||||
):
|
|
||||||
for room in self.rooms.values():
|
for room in self.rooms.values():
|
||||||
await room.broadcast_error(error_message)
|
await room.broadcast_error(error_message)
|
||||||
|
|
||||||
@@ -51,7 +47,7 @@ class Service(object):
|
|||||||
if connection := await room.get_connection_by(attribute, value):
|
if connection := await room.get_connection_by(attribute, value):
|
||||||
return connection
|
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
|
Closes all connections in room
|
||||||
:param room_name: Close name
|
:param room_name: Close name
|
||||||
@@ -64,6 +60,5 @@ class Service(object):
|
|||||||
|
|
||||||
for connection in room.connections.values():
|
for connection in room.connections.values():
|
||||||
await room.disconnect(
|
await room.disconnect(
|
||||||
connection=connection,
|
connection=connection, close_reason=f"Room is closed: {reason}"
|
||||||
close_reason=f'Room is closed: {reason}'
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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 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()
|
service = Service()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from dragonion_server.utils.onion import Onion
|
|
||||||
from dragonion_core.proto.file import AuthFile
|
from dragonion_core.proto.file import AuthFile
|
||||||
from dragonion_server.utils.config.db import services
|
|
||||||
|
|
||||||
from rich import print
|
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:
|
def integrate_onion(port: int, name: str) -> Onion:
|
||||||
"""
|
"""
|
||||||
@@ -24,15 +24,19 @@ def integrate_onion(port: int, name: str) -> Onion:
|
|||||||
onion.cleanup()
|
onion.cleanup()
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
print(f'[green]Available on[/] '
|
print(
|
||||||
f'{(onion_host := onion.start_onion_service(name))}.onion')
|
f"[green]Available on[/] "
|
||||||
|
f"{(onion_host := onion.start_onion_service(name))}.onion"
|
||||||
|
)
|
||||||
|
|
||||||
auth = AuthFile(name)
|
auth = AuthFile(name)
|
||||||
auth['host'] = f'{onion_host}.onion'
|
auth["host"] = f"{onion_host}.onion"
|
||||||
auth['auth'] = onion.auth_string
|
auth["auth"] = onion.auth_string
|
||||||
print(f'To connect to server just share [green]{auth.filename}[/] file')
|
print(f"To connect to server just share [green]{auth.filename}[/] file")
|
||||||
print(f'Or use [#ff901b]service id[/] and [#564ec3]auth string[/]: \n'
|
print(
|
||||||
f'[#ff901b]{onion_host}[/] \n'
|
f"Or use [#ff901b]service id[/] and [#564ec3]auth string[/]: \n"
|
||||||
f'[#564ec3]{services[name].client_auth_priv_key}[/]')
|
f"[#ff901b]{onion_host}[/] \n"
|
||||||
|
f"[#564ec3]{services[name].client_auth_priv_key}[/]"
|
||||||
|
)
|
||||||
|
|
||||||
return onion
|
return onion
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from fastapi import APIRouter, WebSocket
|
from fastapi import APIRouter, WebSocket
|
||||||
from .handlers.websocket_server import serve_websocket
|
|
||||||
|
|
||||||
|
from .handlers.websocket_server import serve_websocket
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
from fastapi import FastAPI
|
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
from fastapi import FastAPI
|
||||||
|
|
||||||
from dragonion_server.utils.onion import get_available_port
|
from dragonion_server.utils.onion import get_available_port
|
||||||
|
|
||||||
from .integration import integrate_onion
|
from .integration import integrate_onion
|
||||||
from .routes import router
|
from .routes import router
|
||||||
|
|
||||||
@@ -14,9 +16,9 @@ def get_app(port: int, name: str) -> FastAPI:
|
|||||||
"""
|
"""
|
||||||
onion = integrate_onion(port, name)
|
onion = integrate_onion(port, name)
|
||||||
return FastAPI(
|
return FastAPI(
|
||||||
title=f'dragonion-server: {name}',
|
title=f"dragonion-server: {name}",
|
||||||
description=f'Secure dragonion chat endpoint server - service {name}',
|
description=f"Secure dragonion chat endpoint server - service {name}",
|
||||||
on_shutdown=[onion.cleanup]
|
on_shutdown=[onion.cleanup],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -31,7 +33,7 @@ def run(name: str, port: int | None = get_available_port()):
|
|||||||
port = get_available_port()
|
port = get_available_port()
|
||||||
app = get_app(port, name)
|
app = get_app(port, name)
|
||||||
app.include_router(router)
|
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()):
|
def run_without_onion(name: str, port: int | None = get_available_port()):
|
||||||
@@ -39,8 +41,8 @@ def run_without_onion(name: str, port: int | None = get_available_port()):
|
|||||||
port = get_available_port()
|
port = get_available_port()
|
||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title=f'dragonion-server: {name}',
|
title=f"dragonion-server: {name}",
|
||||||
description=f'Secure dragonion chat endpoint server - service {name}'
|
description=f"Secure dragonion chat endpoint server - service {name}",
|
||||||
)
|
)
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
uvicorn.run(app, host='0.0.0.0', port=port)
|
uvicorn.run(app, host="0.0.0.0", port=port)
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
from . import db, models
|
from . import db, models
|
||||||
|
|
||||||
__all__ = [
|
__all__ = ["db", "models"]
|
||||||
'db',
|
|
||||||
'models'
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -3,12 +3,8 @@ import sqlitedict
|
|||||||
|
|
||||||
class ConfigDatabase(sqlitedict.SqliteDict):
|
class ConfigDatabase(sqlitedict.SqliteDict):
|
||||||
def __init__(self, tablename):
|
def __init__(self, tablename):
|
||||||
super().__init__(
|
super().__init__(filename="data.storage", tablename=tablename, autocommit=True)
|
||||||
filename='data.storage',
|
|
||||||
tablename=tablename,
|
|
||||||
autocommit=True
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
config = ConfigDatabase('config')
|
config = ConfigDatabase("config")
|
||||||
services = ConfigDatabase('services')
|
services = ConfigDatabase("services")
|
||||||
|
|||||||
@@ -13,5 +13,5 @@ class ServiceModel:
|
|||||||
client_auth_pub_key: str
|
client_auth_pub_key: str
|
||||||
|
|
||||||
service_id: str = None
|
service_id: str = None
|
||||||
key_content: str = 'ED25519-V3'
|
key_content: str = "ED25519-V3"
|
||||||
key_type: str = 'NEW'
|
key_type: str = "NEW"
|
||||||
|
|||||||
@@ -5,28 +5,33 @@ import sys
|
|||||||
|
|
||||||
|
|
||||||
def get_resource_path(filename):
|
def get_resource_path(filename):
|
||||||
application_path = 'resources'
|
application_path = "resources"
|
||||||
|
|
||||||
return os.path.join(application_path, filename)
|
return os.path.join(application_path, filename)
|
||||||
|
|
||||||
|
|
||||||
def get_tor_paths():
|
def get_tor_paths():
|
||||||
if (platform.system() != "Darwin" and
|
if platform.system() != "Darwin" and platform.machine().lower() in [
|
||||||
platform.machine().lower() in ['aarch64', 'arm64']):
|
"aarch64",
|
||||||
if shutil.which('tor'):
|
"arm64",
|
||||||
return 'tor'
|
]:
|
||||||
|
if shutil.which("tor"):
|
||||||
|
return "tor"
|
||||||
else:
|
else:
|
||||||
print('Detected ARM system and tor is not installed or added to PATH. '
|
print(
|
||||||
'Please, consider reading documentation and installing application '
|
"Detected ARM system and tor is not installed or added to PATH. "
|
||||||
'properly')
|
"Please, consider reading documentation and installing application "
|
||||||
|
"properly"
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
from ..onion.tor_downloader import download_tor
|
from ..onion.tor_downloader import download_tor
|
||||||
|
|
||||||
if platform.system() in ["Linux", "Darwin"]:
|
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":
|
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:
|
else:
|
||||||
raise Exception("Platform not supported")
|
raise Exception("Platform not supported")
|
||||||
|
|
||||||
@@ -37,7 +42,7 @@ def get_tor_paths():
|
|||||||
|
|
||||||
|
|
||||||
def build_data_dir():
|
def build_data_dir():
|
||||||
dragonion_data_dir = 'data'
|
dragonion_data_dir = "data"
|
||||||
|
|
||||||
os.makedirs(dragonion_data_dir, exist_ok=True)
|
os.makedirs(dragonion_data_dir, exist_ok=True)
|
||||||
return dragonion_data_dir
|
return dragonion_data_dir
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
from .onion import Onion
|
from .onion import Onion, get_available_port
|
||||||
from .onion import get_available_port
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = ["Onion", "get_available_port"]
|
||||||
'Onion',
|
|
||||||
'get_available_port'
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
from stem.control import Controller
|
import base64
|
||||||
from .stem_process import launch_tor_with_config
|
|
||||||
from stem import ProtocolError
|
|
||||||
|
|
||||||
import socket
|
|
||||||
import random
|
|
||||||
import os
|
import os
|
||||||
import psutil
|
import platform
|
||||||
|
import random
|
||||||
|
import socket
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
import platform
|
|
||||||
import time
|
import time
|
||||||
import base64
|
|
||||||
import nacl.public
|
import nacl.public
|
||||||
|
import psutil
|
||||||
from rich import print
|
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 import config
|
||||||
from dragonion_server.utils.config.db import services
|
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):
|
def get_available_port(min_port: int = 1000, max_port: int = 65535):
|
||||||
@@ -82,18 +82,18 @@ class Onion(object):
|
|||||||
self.kill_same_tor()
|
self.kill_same_tor()
|
||||||
|
|
||||||
tor_config = {
|
tor_config = {
|
||||||
'DataDirectory': tor_data_directory_name,
|
"DataDirectory": tor_data_directory_name,
|
||||||
'SocksPort': str(self.tor_socks_port),
|
"SocksPort": str(self.tor_socks_port),
|
||||||
'CookieAuthentication': '1',
|
"CookieAuthentication": "1",
|
||||||
'CookieAuthFile': self.tor_cookie_auth_file,
|
"CookieAuthFile": self.tor_cookie_auth_file,
|
||||||
'AvoidDiskWrites': '1',
|
"AvoidDiskWrites": "1",
|
||||||
'Log': [
|
"Log": ["NOTICE stdout"],
|
||||||
'NOTICE stdout'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if platform.system() in ["Windows", "Darwin"] or \
|
if (
|
||||||
len(tor_data_directory_name) > 90:
|
platform.system() in ["Windows", "Darwin"]
|
||||||
|
or len(tor_data_directory_name) > 90
|
||||||
|
):
|
||||||
try:
|
try:
|
||||||
self.tor_control_port = get_available_port(1000, 65535)
|
self.tor_control_port = get_available_port(1000, 65535)
|
||||||
tor_config = tor_config | {"ControlPort": str(self.tor_control_port)}
|
tor_config = tor_config | {"ControlPort": str(self.tor_control_port)}
|
||||||
@@ -102,24 +102,22 @@ class Onion(object):
|
|||||||
self.tor_control_socket = None
|
self.tor_control_socket = None
|
||||||
else:
|
else:
|
||||||
self.tor_control_port = None
|
self.tor_control_port = None
|
||||||
self.tor_control_socket = os.path.abspath(os.path.join(
|
self.tor_control_socket = os.path.abspath(
|
||||||
tor_data_directory_name, "control_socket"
|
os.path.join(tor_data_directory_name, "control_socket")
|
||||||
))
|
)
|
||||||
tor_config = tor_config | {"ControlSocket": str(self.tor_control_socket)}
|
tor_config = tor_config | {"ControlSocket": str(self.tor_control_socket)}
|
||||||
|
|
||||||
return tor_config
|
return tor_config
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
self.tor_data_directory = tempfile.TemporaryDirectory(
|
self.tor_data_directory = tempfile.TemporaryDirectory(dir=dirs.build_tmp_dir())
|
||||||
dir=dirs.build_tmp_dir()
|
|
||||||
)
|
|
||||||
self.tor_data_directory_name = self.tor_data_directory.name
|
self.tor_data_directory_name = self.tor_data_directory.name
|
||||||
|
|
||||||
self.tor_proc = launch_tor_with_config(
|
self.tor_proc = launch_tor_with_config(
|
||||||
config=self.get_config(self.tor_data_directory_name),
|
config=self.get_config(self.tor_data_directory_name),
|
||||||
tor_cmd=self.tor_path,
|
tor_cmd=self.tor_path,
|
||||||
take_ownership=True,
|
take_ownership=True,
|
||||||
init_msg_handler=print
|
init_msg_handler=print,
|
||||||
)
|
)
|
||||||
|
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
@@ -149,14 +147,12 @@ class Onion(object):
|
|||||||
|
|
||||||
client_auth_priv_key_raw = nacl.public.PrivateKey.generate()
|
client_auth_priv_key_raw = nacl.public.PrivateKey.generate()
|
||||||
client_auth_priv_key = key_str(client_auth_priv_key_raw)
|
client_auth_priv_key = key_str(client_auth_priv_key_raw)
|
||||||
client_auth_pub_key = key_str(
|
client_auth_pub_key = key_str(client_auth_priv_key_raw.public_key)
|
||||||
client_auth_priv_key_raw.public_key
|
|
||||||
)
|
|
||||||
|
|
||||||
services[name] = config.models.ServiceModel(
|
services[name] = config.models.ServiceModel(
|
||||||
port=port,
|
port=port,
|
||||||
client_auth_priv_key=client_auth_priv_key,
|
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]
|
return services[name]
|
||||||
|
|
||||||
@@ -167,7 +163,7 @@ class Onion(object):
|
|||||||
:return: .onion url
|
:return: .onion url
|
||||||
"""
|
"""
|
||||||
if name not in services.keys():
|
if name not in services.keys():
|
||||||
raise 'Service not created'
|
raise "Service not created"
|
||||||
|
|
||||||
service: config.models.ServiceModel = services[name]
|
service: config.models.ServiceModel = services[name]
|
||||||
|
|
||||||
@@ -193,8 +189,9 @@ class Onion(object):
|
|||||||
service.key_type = "ED25519-V3"
|
service.key_type = "ED25519-V3"
|
||||||
service.key_content = res.private_key
|
service.key_content = res.private_key
|
||||||
|
|
||||||
self.auth_string = f'{res.service_id}:descriptor:' \
|
self.auth_string = (
|
||||||
f'x25519:{service.client_auth_priv_key}'
|
f"{res.service_id}:descriptor:" f"x25519:{service.client_auth_priv_key}"
|
||||||
|
)
|
||||||
|
|
||||||
services[name] = service
|
services[name] = service
|
||||||
|
|
||||||
@@ -204,9 +201,7 @@ class Onion(object):
|
|||||||
service: config.models.ServiceModel = services[name]
|
service: config.models.ServiceModel = services[name]
|
||||||
if service.service_id:
|
if service.service_id:
|
||||||
try:
|
try:
|
||||||
self.c.remove_ephemeral_hidden_service(
|
self.c.remove_ephemeral_hidden_service(service.service_id)
|
||||||
service.service_id
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
|
|
||||||
@@ -237,9 +232,7 @@ class Onion(object):
|
|||||||
num_rend_circuits += 1
|
num_rend_circuits += 1
|
||||||
|
|
||||||
if num_rend_circuits == 0:
|
if num_rend_circuits == 0:
|
||||||
print(
|
print("\rTor rendezvous circuits have closed" + " " * 20)
|
||||||
"\rTor rendezvous circuits have closed" + " " * 20
|
|
||||||
)
|
|
||||||
break
|
break
|
||||||
|
|
||||||
if num_rend_circuits == 1:
|
if num_rend_circuits == 1:
|
||||||
@@ -271,7 +264,7 @@ class Onion(object):
|
|||||||
try:
|
try:
|
||||||
self.tor_data_directory.cleanup()
|
self.tor_data_directory.cleanup()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'Cannot cleanup temporary directory: {e}')
|
print(f"Cannot cleanup temporary directory: {e}")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def get_tor_socks_port(self):
|
def get_tor_socks_port(self):
|
||||||
|
|||||||
@@ -15,13 +15,21 @@ import stem.util.str_tools
|
|||||||
import stem.util.system
|
import stem.util.system
|
||||||
import stem.version
|
import stem.version
|
||||||
|
|
||||||
NO_TORRC = '<no torrc>'
|
NO_TORRC = "<no torrc>"
|
||||||
DEFAULT_INIT_TIMEOUT = 90
|
DEFAULT_INIT_TIMEOUT = 90
|
||||||
|
|
||||||
|
|
||||||
def launch_tor(tor_cmd='tor', args=None, torrc_path=None, completion_percent=100,
|
def launch_tor(
|
||||||
init_msg_handler=None, timeout=DEFAULT_INIT_TIMEOUT,
|
tor_cmd="tor",
|
||||||
take_ownership=False, close_output=True, stdin=None):
|
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
|
Initializes a tor process. This blocks until initialization completes or we
|
||||||
error out.
|
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 stem.util.system.is_windows():
|
||||||
if timeout is not None and timeout != DEFAULT_INIT_TIMEOUT:
|
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
|
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:
|
if timeout is not None and timeout != DEFAULT_INIT_TIMEOUT:
|
||||||
raise OSError(
|
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
|
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):
|
elif not os.path.isfile(tor_cmd):
|
||||||
raise OSError("'%s' doesn't exist" % tor_cmd)
|
raise OSError("'%s' doesn't exist" % tor_cmd)
|
||||||
elif not stem.util.system.is_available(tor_cmd):
|
elif not stem.util.system.is_available(tor_cmd):
|
||||||
raise OSError(f"{tor_cmd} isn't available on your system. "
|
raise OSError(
|
||||||
f"Maybe it's not in your PATH?")
|
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
|
# 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):
|
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:
|
||||||
if torrc_path == NO_TORRC:
|
if torrc_path == NO_TORRC:
|
||||||
temp_file = tempfile.mkstemp(prefix='empty-torrc-', text=True)[1]
|
temp_file = tempfile.mkstemp(prefix="empty-torrc-", text=True)[1]
|
||||||
runtime_args += ['-f', temp_file]
|
runtime_args += ["-f", temp_file]
|
||||||
else:
|
else:
|
||||||
runtime_args += ['-f', torrc_path]
|
runtime_args += ["-f", torrc_path]
|
||||||
|
|
||||||
if take_ownership:
|
if take_ownership:
|
||||||
runtime_args += ['__OwningControllerProcess', str(os.getpid())]
|
runtime_args += ["__OwningControllerProcess", str(os.getpid())]
|
||||||
|
|
||||||
tor_process = None
|
tor_process = None
|
||||||
|
|
||||||
@@ -119,8 +130,9 @@ def launch_tor(tor_cmd='tor', args=None, torrc_path=None, completion_percent=100
|
|||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE,
|
stderr=subprocess.PIPE,
|
||||||
env=None if platform.system() == 'Windows' else
|
env=None
|
||||||
{"LD_LIBRARY_PATH": os.path.dirname(tor_cmd)}
|
if platform.system() == "Windows"
|
||||||
|
else {"LD_LIBRARY_PATH": os.path.dirname(tor_cmd)},
|
||||||
)
|
)
|
||||||
|
|
||||||
if stdin:
|
if stdin:
|
||||||
@@ -129,15 +141,16 @@ def launch_tor(tor_cmd='tor', args=None, torrc_path=None, completion_percent=100
|
|||||||
tor_process.stdin.close()
|
tor_process.stdin.close()
|
||||||
|
|
||||||
if timeout:
|
if timeout:
|
||||||
|
|
||||||
def timeout_handler(*_):
|
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.signal(signal.SIGALRM, timeout_handler)
|
||||||
signal.setitimer(signal.ITIMER_REAL, timeout)
|
signal.setitimer(signal.ITIMER_REAL, timeout)
|
||||||
|
|
||||||
bootstrap_line = re.compile('Bootstrapped ([0-9]+)%')
|
bootstrap_line = re.compile("Bootstrapped ([0-9]+)%")
|
||||||
problem_line = re.compile('\\[(warn|err)] (.*)$')
|
problem_line = re.compile("\\[(warn|err)] (.*)$")
|
||||||
last_problem = 'Timed out'
|
last_problem = "Timed out"
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
# Tor's stdout will be read as ASCII bytes. This is fine for python 2, but
|
# 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
|
# It seems like python 2.x is perfectly happy for this to be unicode, so
|
||||||
# normalizing to that.
|
# 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
|
# this will provide empty results if the process is terminated
|
||||||
|
|
||||||
if not init_line:
|
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
|
# 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:
|
elif problem_match:
|
||||||
runlevel, msg = problem_match.groups()
|
runlevel, msg = problem_match.groups()
|
||||||
|
|
||||||
if 'see warnings above' not in msg:
|
if "see warnings above" not in msg:
|
||||||
if ': ' in msg:
|
if ": " in msg:
|
||||||
msg = msg.split(': ')[-1].strip()
|
msg = msg.split(": ")[-1].strip()
|
||||||
|
|
||||||
last_problem = msg
|
last_problem = msg
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -199,9 +212,15 @@ def launch_tor(tor_cmd='tor', args=None, torrc_path=None, completion_percent=100
|
|||||||
assert e
|
assert e
|
||||||
|
|
||||||
|
|
||||||
def launch_tor_with_config(config, tor_cmd='tor', completion_percent=100,
|
def launch_tor_with_config(
|
||||||
init_msg_handler=None, timeout=DEFAULT_INIT_TIMEOUT,
|
config,
|
||||||
take_ownership=False, close_output=True):
|
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
|
Initializes a tor process, like :func:`~stem.process.launch_tor`, but with a
|
||||||
customized configuration. This writes a temporary torrc to disk, launches
|
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:
|
try:
|
||||||
use_stdin = stem.version.get_system_tor_version(
|
use_stdin = (
|
||||||
tor_cmd) >= stem.version.Requirement.TORRC_VIA_STDIN
|
stem.version.get_system_tor_version(tor_cmd)
|
||||||
|
>= stem.version.Requirement.TORRC_VIA_STDIN
|
||||||
|
)
|
||||||
except IOError:
|
except IOError:
|
||||||
use_stdin = False
|
use_stdin = False
|
||||||
|
|
||||||
# we need to be sure that we're logging to stdout to figure out when we're
|
# we need to be sure that we're logging to stdout to figure out when we're
|
||||||
# done bootstrapping
|
# done bootstrapping
|
||||||
|
|
||||||
if 'Log' in config:
|
if "Log" in config:
|
||||||
stdout_options = ['DEBUG stdout', 'INFO stdout', 'NOTICE stdout']
|
stdout_options = ["DEBUG stdout", "INFO stdout", "NOTICE stdout"]
|
||||||
|
|
||||||
if isinstance(config['Log'], str):
|
if isinstance(config["Log"], str):
|
||||||
config['Log'] = [config['Log']]
|
config["Log"] = [config["Log"]]
|
||||||
|
|
||||||
has_stdout = False
|
has_stdout = False
|
||||||
|
|
||||||
for log_config in config['Log']:
|
for log_config in config["Log"]:
|
||||||
if log_config in stdout_options:
|
if log_config in stdout_options:
|
||||||
has_stdout = True
|
has_stdout = True
|
||||||
break
|
break
|
||||||
|
|
||||||
if not has_stdout:
|
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()):
|
for key, values in list(config.items()):
|
||||||
if isinstance(values, str):
|
if isinstance(values, str):
|
||||||
config_str += '%s %s\n' % (key, values)
|
config_str += "%s %s\n" % (key, values)
|
||||||
else:
|
else:
|
||||||
for value in values:
|
for value in values:
|
||||||
config_str += '%s %s\n' % (key, value)
|
config_str += "%s %s\n" % (key, value)
|
||||||
|
|
||||||
if use_stdin:
|
if use_stdin:
|
||||||
return launch_tor(
|
return launch_tor(
|
||||||
tor_cmd=tor_cmd,
|
tor_cmd=tor_cmd,
|
||||||
args=['-f', '-'],
|
args=["-f", "-"],
|
||||||
completion_percent=completion_percent,
|
completion_percent=completion_percent,
|
||||||
init_msg_handler=init_msg_handler,
|
init_msg_handler=init_msg_handler,
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
take_ownership=take_ownership,
|
take_ownership=take_ownership,
|
||||||
close_output=close_output,
|
close_output=close_output,
|
||||||
stdin=config_str
|
stdin=config_str,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
torrc_descriptor, torrc_path = tempfile.mkstemp(prefix='torrc-', text=True)
|
torrc_descriptor, torrc_path = tempfile.mkstemp(prefix="torrc-", text=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(torrc_path, 'w') as torrc_file:
|
with open(torrc_path, "w") as torrc_file:
|
||||||
torrc_file.write(config_str)
|
torrc_file.write(config_str)
|
||||||
|
|
||||||
# prevents tor from error-ing out due to a missing torrc if it gets a sighup
|
# 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,
|
return launch_tor(
|
||||||
init_msg_handler, timeout, take_ownership)
|
tor_cmd,
|
||||||
|
args,
|
||||||
|
torrc_path,
|
||||||
|
completion_percent,
|
||||||
|
init_msg_handler,
|
||||||
|
timeout,
|
||||||
|
take_ownership,
|
||||||
|
)
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
os.close(torrc_descriptor)
|
os.close(torrc_descriptor)
|
||||||
|
|||||||
@@ -1,51 +1,51 @@
|
|||||||
import os
|
|
||||||
import io
|
import io
|
||||||
import tarfile
|
import os
|
||||||
import requests
|
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
import tarfile
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
def get_latest_version() -> str:
|
def get_latest_version() -> str:
|
||||||
"""
|
"""
|
||||||
Gets latest non-alfa version name from dist.torproject.org
|
Gets latest non-alfa version name from dist.torproject.org
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
r = requests.get('https://dist.torproject.org/torbrowser/').text
|
r = requests.get("https://dist.torproject.org/torbrowser/").text
|
||||||
|
|
||||||
results = re.findall(r'<a href=".+/">(.+)/</a>', r)
|
results = re.findall(r'<a href=".+/">(.+)/</a>', r)
|
||||||
for res in results:
|
for res in results:
|
||||||
if 'a' not in res:
|
if "a" not in res:
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
def get_build() -> Literal[
|
def get_build() -> Literal[
|
||||||
'windows-x86_64',
|
"windows-x86_64", "linux-x86_64", "macos-x86_64", "macos-aarch64"
|
||||||
'linux-x86_64',
|
|
||||||
'macos-x86_64',
|
|
||||||
'macos-aarch64'
|
|
||||||
]:
|
]:
|
||||||
"""
|
"""
|
||||||
Gets proper build name for your system
|
Gets proper build name for your system
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if sys.platform == 'win32':
|
if sys.platform == "win32":
|
||||||
return 'windows-x86_64'
|
return "windows-x86_64"
|
||||||
elif sys.platform == 'linux':
|
elif sys.platform == "linux":
|
||||||
return 'linux-x86_64'
|
return "linux-x86_64"
|
||||||
elif sys.platform == 'darwin':
|
elif sys.platform == "darwin":
|
||||||
import platform
|
import platform
|
||||||
if platform.uname().machine == 'arm64':
|
|
||||||
return 'macos-aarch64'
|
if platform.uname().machine == "arm64":
|
||||||
|
return "macos-aarch64"
|
||||||
else:
|
else:
|
||||||
return 'macos-x86_64'
|
return "macos-x86_64"
|
||||||
else:
|
else:
|
||||||
raise 'System not supported'
|
raise "System not supported"
|
||||||
|
|
||||||
|
|
||||||
def get_tor_expert_bundles(version: str = get_latest_version(),
|
def get_tor_expert_bundles(
|
||||||
platform: str = get_build()):
|
version: str = get_latest_version(), platform: str = get_build()
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Returns a link for downloading tor expert bundle by version and platform
|
Returns a link for downloading tor expert bundle by version and platform
|
||||||
:param version: Tor expert bundle version that exists in dist.torproject.org
|
: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()
|
get_build()
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
return f'https://dist.torproject.org/torbrowser/{version}/tor-expert-bundle-' \
|
return (
|
||||||
f'{version}-{platform}.tar.gz'
|
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
|
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
|
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):
|
if not os.path.exists(dist):
|
||||||
os.makedirs(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=[
|
members=[
|
||||||
tarinfo
|
tarinfo for tarinfo in tar.getmembers() if tarinfo.name.startswith("tor/")
|
||||||
for tarinfo
|
],
|
||||||
in tar.getmembers()
|
path=dist,
|
||||||
if tarinfo.name.startswith("tor/")
|
)
|
||||||
], path=dist)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
download_tor()
|
download_tor()
|
||||||
|
|||||||
Reference in New Issue
Block a user