import re import enum import threading import socket import selectors import ssl from configparser import ConfigParser from nntp.tiny.config import ( ConfigException, ConfigSectionException, ConfigValueException) from nntp.tiny.db import Database 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: ConfigParser): self.config = config self.capabilities = ServerCapability.NONE self.newsgroups = dict() self.sslctx = None if config['listen'].get('tls', 'no') == 'yes': self.sslctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) self.sslctx.load_cert_chain(config['tls']['cert'], config['tls']['key']) self._init_newsgroups() def connect_to_db(self): return Database.connect(self.config['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 as e: pass thread = threading.Thread(target=spawn) thread.start() def run(self): if not self.config.has_section('listen'): raise ConfigSectionException('listen') if not self.config.has_option('listen', 'host'): raise ConfigValueException('listen', 'host') hosts = re.split(r'\s*,\s*', self.config['listen']['host']) port = int(self.config['listen']['port']) listeners = list() for host in hosts: if host.find(':') < 0: listeners.append(self.listen(host, port, socket.AF_INET)) else: listeners.append(self.listen(host, port, socket.AF_INET6)) 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)