xenu_nntp/lib/nntp/tiny/server.py
2024-12-04 23:28:25 -05:00

98 lines
2.8 KiB
Python

import re
import enum
import threading
import socket
import selectors
import ssl
from nntp.tiny.config import Config, ConfigException
from nntp.tiny.db import Database
from nntp.tiny.host import Host
from nntp.tiny.newsgroup import Newsgroup
from nntp.tiny.session import Session
class ServerCapability(enum.Flag):
NONE = 0
AUTH = enum.auto()
POST = enum.auto()
class Server():
def __init__(self, config: Config):
self.config = config
self.capabilities = ServerCapability.NONE
self.newsgroups = dict()
self.sslctx = None
if config.section('listen').get('tls', 'no') == 'yes':
self.sslctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
self.sslctx.load_cert_chain(config.get('tls', 'cert'),
config.get('tls', 'key'))
self._init_newsgroups()
def connect_to_db(self):
return Database.connect(self.config.get('database', 'path'))
def _init_newsgroups(self):
db = self.connect_to_db()
for newsgroup in db.query(Newsgroup).each():
self.newsgroups[newsgroup.name.casefold()] = newsgroup
def listen(self, host: str, port: int, af: int):
listener = socket.socket(af, socket.SOCK_STREAM)
listener.bind((host, port))
listener.listen()
if self.sslctx:
return self.sslctx.wrap_socket(listener, server_side=True)
return listener
def accept(self, listener):
sock, addr = None, None
try:
sock, addr = listener.accept()
except ssl.SSLError as e:
return
def spawn():
session = Session(self, sock)
try:
session.handle()
except (ssl.SSLEOFError, ssl.SSLError):
pass
thread = threading.Thread(target=spawn)
thread.start()
def run(self):
hosts = re.split(r'\s*,\s*', self.config.get('listen', 'host'))
port = int(self.config.get('listen', 'port'))
listeners = list()
for host in hosts:
if Host.is_ipv6(host):
listeners.append(self.listen(host, port, socket.AF_INET6))
elif Host.is_ipv4(host):
listeners.append(self.listen(host, port, socket.AF_INET))
else:
for af in (socket.AF_INET, socket.AF_INET6):
listeners.append(self.listen(host, port, af))
if len(listeners) == 0:
raise ConfigException('No listener hosts specified')
sel = selectors.DefaultSelector()
for listener in listeners:
sel.register(listener, selectors.EVENT_READ)
while True:
events = sel.select()
for key, ev in events:
self.accept(key.fileobj)