91 lines
2.5 KiB
Python
91 lines
2.5 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.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.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'))
|
|
|
|
def connect_to_db(self):
|
|
return Database.connect(self.config.get('database', 'path'))
|
|
|
|
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)
|
|
|
|
def stop(self):
|
|
...
|