first commit
This commit is contained in:
+113
@@ -0,0 +1,113 @@
|
||||
"""Einstiegspunkt: startet HTTP-/SOCKS-Proxy, Weiterleitungen und WebUI in einer Event-Loop."""
|
||||
import asyncio
|
||||
import logging
|
||||
import secrets
|
||||
import socket
|
||||
import time
|
||||
|
||||
import uvicorn
|
||||
|
||||
from . import config
|
||||
from .api import app
|
||||
from .auth import hash_password, users
|
||||
from .core import history, log, peer_ip
|
||||
from .db import db
|
||||
from .forwarder import forwards
|
||||
from .policy import policy
|
||||
from .proxy_http import handle_http
|
||||
from .proxy_socks import handle_socks5
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
|
||||
|
||||
|
||||
def bootstrap() -> None:
|
||||
if db.one("SELECT COUNT(*) n FROM users")["n"] == 0:
|
||||
password = config.ADMIN_PASSWORD or secrets.token_urlsafe(12)
|
||||
db.execute("INSERT INTO users(username,password_hash,is_admin,enabled,policy,comment,created_at) "
|
||||
"VALUES(?,?,1,1,'inherit','Initialer Administrator',?)",
|
||||
(config.ADMIN_USER, hash_password(password), time.time()))
|
||||
log.warning("=" * 60)
|
||||
log.warning("Admin-Benutzer angelegt: %s", config.ADMIN_USER)
|
||||
if not config.ADMIN_PASSWORD:
|
||||
log.warning("Generiertes Passwort: %s", password)
|
||||
log.warning("=" * 60)
|
||||
users.reload()
|
||||
policy.reload()
|
||||
|
||||
|
||||
async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
|
||||
"""Erkennt anhand des ersten Bytes, ob SOCKS5 oder HTTP gesprochen wird –
|
||||
beide Proxy-Ports verstehen daher beide Protokolle."""
|
||||
client_ip = peer_ip(writer)
|
||||
sock = writer.get_extra_info("socket")
|
||||
if sock is not None:
|
||||
try:
|
||||
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
first = await asyncio.wait_for(reader.readexactly(1), timeout=60)
|
||||
if first == b"\x05":
|
||||
await handle_socks5(reader, writer, client_ip)
|
||||
elif first == b"\x04":
|
||||
pass # SOCKS4 wird nicht unterstützt
|
||||
else:
|
||||
await handle_http(reader, writer, first, client_ip)
|
||||
except (asyncio.IncompleteReadError, asyncio.TimeoutError, ConnectionError, OSError):
|
||||
pass
|
||||
except Exception: # noqa: BLE001
|
||||
log.exception("Fehler bei Verbindung von %s", client_ip)
|
||||
finally:
|
||||
try:
|
||||
writer.close()
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
|
||||
|
||||
async def maintenance() -> None:
|
||||
last_cleanup = 0.0
|
||||
while True:
|
||||
await asyncio.sleep(1)
|
||||
try:
|
||||
await asyncio.to_thread(history.flush)
|
||||
if time.time() - last_cleanup > 3600:
|
||||
last_cleanup = time.time()
|
||||
users.flush_last_seen()
|
||||
days = int(policy.settings.get("log_retention_days", "90") or 0)
|
||||
if days > 0:
|
||||
n = db.execute("DELETE FROM history WHERE ts<?", (time.time() - days * 86400,)).rowcount
|
||||
if n:
|
||||
log.info("Verlauf bereinigt: %d Einträge älter als %d Tage gelöscht", n, days)
|
||||
db.execute("DELETE FROM tokens WHERE expires_at IS NOT NULL AND expires_at<?", (time.time(),))
|
||||
except Exception: # noqa: BLE001
|
||||
log.exception("Wartungsfehler")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
bootstrap()
|
||||
servers = []
|
||||
ports = dict.fromkeys([config.HTTP_PORT, config.SOCKS_PORT])
|
||||
for port in ports:
|
||||
srv = await asyncio.start_server(handle_client, host=config.LISTEN_HOST, port=port, limit=256 * 1024,
|
||||
backlog=1024, reuse_address=True)
|
||||
servers.append(srv)
|
||||
log.info("Proxy lauscht auf %s:%d (HTTP + SOCKS5)", config.LISTEN_HOST, port)
|
||||
await forwards.reload()
|
||||
|
||||
web = uvicorn.Server(uvicorn.Config(app, host=config.LISTEN_HOST, port=config.WEB_PORT, log_level="warning",
|
||||
proxy_headers=True, forwarded_allow_ips="*"))
|
||||
log.info("WebUI/API auf http://%s:%d (API-Doku: /api/docs)", config.LISTEN_HOST, config.WEB_PORT)
|
||||
background = [asyncio.create_task(c) for c in (maintenance(), forwards.housekeeping(),
|
||||
*(s.serve_forever() for s in servers))]
|
||||
try:
|
||||
await web.serve() # beendet sich bei SIGTERM/SIGINT
|
||||
finally:
|
||||
for t in background:
|
||||
t.cancel()
|
||||
history.flush()
|
||||
users.flush_last_seen()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user