2024-12-04 09:08:39 -05:00
|
|
|
import re
|
2024-11-20 21:17:03 -05:00
|
|
|
import enum
|
2024-12-03 23:38:45 -05:00
|
|
|
import threading
|
2024-11-26 16:55:44 -05:00
|
|
|
import socket
|
2024-12-03 23:38:45 -05:00
|
|
|
import selectors
|
2024-12-03 12:14:45 -05:00
|
|
|
import ssl
|
2024-11-20 21:17:03 -05:00
|
|
|
|
2024-12-04 11:48:56 -05:00
|
|
|
from nntp.tiny.config import Config, ConfigException
|
2024-12-02 23:19:01 -05:00
|
|
|
from nntp.tiny.db import Database
|
2024-11-22 23:57:14 -05:00
|
|
|
from nntp.tiny.newsgroup import Newsgroup
|
2024-11-26 16:55:44 -05:00
|
|
|
from nntp.tiny.session import Session
|
2024-11-22 23:57:14 -05:00
|
|
|
|
2024-11-20 21:17:03 -05:00
|
|
|
class ServerCapability(enum.Flag):
|
|
|
|
NONE = 0
|
|
|
|
AUTH = enum.auto()
|
|
|
|
POST = enum.auto()
|
|
|
|
|
|
|
|
class Server():
|
2024-12-04 11:48:56 -05:00
|
|
|
def __init__(self, config: Config):
|
2024-12-02 23:19:01 -05:00
|
|
|
self.config = config
|
|
|
|
self.capabilities = ServerCapability.NONE
|
|
|
|
self.newsgroups = dict()
|
2024-12-03 12:14:45 -05:00
|
|
|
self.sslctx = None
|
|
|
|
|
|
|
|
if config['listen'].get('tls', 'no') == 'yes':
|
|
|
|
self.sslctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
2024-12-04 11:48:56 -05:00
|
|
|
self.sslctx.load_cert_chain(config.get('tls', 'cert'),
|
|
|
|
config.get('tls', 'key'))
|
2024-11-22 23:57:14 -05:00
|
|
|
|
2024-11-25 00:16:15 -05:00
|
|
|
self._init_newsgroups()
|
2024-11-22 23:57:14 -05:00
|
|
|
|
2024-12-02 23:19:01 -05:00
|
|
|
def connect_to_db(self):
|
2024-12-04 11:48:56 -05:00
|
|
|
return Database.connect(self.config.get('database', 'path'))
|
2024-12-02 23:19:01 -05:00
|
|
|
|
2024-11-22 23:57:14 -05:00
|
|
|
def _init_newsgroups(self):
|
2024-11-26 16:24:32 -05:00
|
|
|
db = self.connect_to_db()
|
|
|
|
|
|
|
|
for newsgroup in db.query(Newsgroup).each():
|
2024-11-22 23:57:14 -05:00
|
|
|
self.newsgroups[newsgroup.name.casefold()] = newsgroup
|
2024-11-26 16:55:44 -05:00
|
|
|
|
2024-12-03 23:38:45 -05:00
|
|
|
def listen(self, host: str, port: int, af: int):
|
|
|
|
listener = socket.socket(af, socket.SOCK_STREAM)
|
2024-12-02 23:19:01 -05:00
|
|
|
listener.bind((host, port))
|
2024-11-26 16:55:44 -05:00
|
|
|
listener.listen()
|
|
|
|
|
2024-12-03 12:14:45 -05:00
|
|
|
if self.sslctx:
|
2024-12-03 23:38:45 -05:00
|
|
|
return self.sslctx.wrap_socket(listener, server_side=True)
|
2024-12-03 12:14:45 -05:00
|
|
|
|
2024-12-03 23:38:45 -05:00
|
|
|
return listener
|
|
|
|
|
|
|
|
def accept(self, listener):
|
2024-12-04 09:08:39 -05:00
|
|
|
sock, addr = None, None
|
|
|
|
|
|
|
|
try:
|
|
|
|
sock, addr = listener.accept()
|
|
|
|
except ssl.SSLError as e:
|
|
|
|
return
|
2024-12-03 23:38:45 -05:00
|
|
|
|
2024-12-03 23:50:12 -05:00
|
|
|
def spawn():
|
|
|
|
session = Session(self, sock)
|
|
|
|
|
|
|
|
try:
|
2024-12-03 23:38:45 -05:00
|
|
|
session.handle()
|
2024-12-04 09:08:39 -05:00
|
|
|
except ssl.SSLEOFError as e:
|
2024-12-03 23:50:12 -05:00
|
|
|
pass
|
2024-12-03 23:38:45 -05:00
|
|
|
|
2024-12-03 23:50:12 -05:00
|
|
|
thread = threading.Thread(target=spawn)
|
|
|
|
thread.start()
|
2024-11-26 16:55:44 -05:00
|
|
|
|
2024-12-04 10:08:17 -05:00
|
|
|
@staticmethod
|
|
|
|
def _is_ipv6(value: str):
|
|
|
|
return value.find(':') >= 0
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _is_ipv4(value: str):
|
|
|
|
parts = value.split('.')
|
|
|
|
|
|
|
|
if len(parts) > 4 or len(parts) == 0:
|
|
|
|
return False
|
|
|
|
|
|
|
|
for part in parts:
|
|
|
|
if not part.isdecimal():
|
|
|
|
return False
|
|
|
|
|
|
|
|
num = int(part)
|
|
|
|
|
|
|
|
if num > 255:
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
2024-12-03 23:38:45 -05:00
|
|
|
def run(self):
|
2024-12-04 11:48:56 -05:00
|
|
|
hosts = re.split(r'\s*,\s*', self.config.get('listen', 'host'))
|
|
|
|
port = int(self.config.get('listen', 'port'))
|
2024-12-04 09:08:39 -05:00
|
|
|
|
|
|
|
listeners = list()
|
2024-12-03 23:38:45 -05:00
|
|
|
|
2024-12-04 09:08:39 -05:00
|
|
|
for host in hosts:
|
2024-12-04 10:08:17 -05:00
|
|
|
if Server._is_ipv6(host):
|
|
|
|
listeners.append(self.listen(host, port, socket.AF_INET6))
|
|
|
|
elif Server._is_ipv4(host):
|
2024-12-04 09:08:39 -05:00
|
|
|
listeners.append(self.listen(host, port, socket.AF_INET))
|
|
|
|
else:
|
2024-12-04 10:08:17 -05:00
|
|
|
for af in (socket.AF_INET, socket.AF_INET6):
|
|
|
|
listeners.append(self.listen(host, port, af))
|
2024-11-26 17:01:49 -05:00
|
|
|
|
2024-12-03 23:38:45 -05:00
|
|
|
if len(listeners) == 0:
|
2024-12-04 09:08:39 -05:00
|
|
|
raise ConfigException('No listener hosts specified')
|
2024-12-03 23:38:45 -05:00
|
|
|
|
|
|
|
sel = selectors.DefaultSelector()
|
|
|
|
|
|
|
|
for listener in listeners:
|
|
|
|
sel.register(listener, selectors.EVENT_READ)
|
|
|
|
|
|
|
|
while True:
|
|
|
|
events = sel.select()
|
2024-11-26 16:55:44 -05:00
|
|
|
|
2024-12-03 23:38:45 -05:00
|
|
|
for key, ev in events:
|
|
|
|
self.accept(key.fileobj)
|