Gangly bones of the new sever logic

This commit is contained in:
XANTRONIX Development 2024-11-20 21:17:03 -05:00
parent 0e5bcd5f77
commit 282f43679c
4 changed files with 183 additions and 0 deletions

15
lib/nntp/tiny/request.py Normal file
View file

@ -0,0 +1,15 @@
import socket
class NNTPRequest():
__slots__ = 'buf', 'offset', 'sock',
BUFFER_SIZE = 4096
def __init__(self, sock: socket.socket):
self.buf: bytearray = bytearray(self.BUFFER_SIZE)
self.offset: int = 0
self.sock: socket.socket = sock

85
lib/nntp/tiny/response.py Normal file
View file

@ -0,0 +1,85 @@
import enum
import socket
from typing import Optional
class ResponseCode(enum.Enum):
NNTP_HELP_FOLLOWS = 100
NNTP_CAPABILITIES_FOLLOW = 101
NNTP_DATE = 111
NNTP_SERVICE_READY_POST_ALLOWED = 200
NNTP_SERVICE_READY_POST_PROHIBITED = 201
NNTP_CONNECTION_CLOSING = 205
NNTP_GROUP_LISTING = 211
NNTP_INFORMATION_FOLLOWS = 215
NNTP_ARTICLE_BODY = 220
NNTP_ARTICLE_LISTING = 221
NNTP_BODY_LISTING = 222
NNTP_ARTICLE_STAT_RESPONSE = 223
NNTP_OVERVIEW_FOLLOWS = 224
NNTP_HEADERS_FOLLOW = 225
NNTP_ARTICLE_LISTING_ID_FOLLOWS = 230
NNTP_GROUPS_NEW_FOLLOW = 231
NNTP_ARTICLE_RECEIVED = 240
NNTP_AUTH_ACCEPTED = 281
NNTP_INQUIRY_ARTICLE = 340
NNTP_INQUIRY_PASSPHRASE = 381
NNTP_NEWSGROUP_NOT_FOUND = 411
NNTP_NEWSGROUP_NOT_SELECTED = 412
NNTP_ARTICLE_INVALID_NUMBER = 420
NNTP_ARTICLE_NOT_FOUND_NUM = 423
NNTP_ARTICLE_NOT_FOUND_ID = 430
NNTP_POST_PROHIBITED = 440
NNTP_POST_FAILED = 441
NNTP_AUTH_FAILED = 481
NNTP_AUTH_BAD_SEQUENCE = 482
NNTP_COMMAND_UNKNOWN = 500
NNTP_SYNTAX_ERROR = 501
NNTP_COMMAND_UNAVAILABLE = 502
NNTP_GROUPS_UNAVAILABLE = 503
def message(self):
return {
100: "Help text follows",
101: "Capabilities follow",
200: "NNTP Service Ready, posting allowed",
201: "NNTP Service Ready, posting prohibited",
205: "Connection closing",
215: "Information follows",
224: "Overview information follows (multi-line)",
225: "Headers follow (multi-line)",
231: "List of new newsgroups follows",
240: "Article received OK",
281: "Authentication accepted",
340: "Input article; end with <CR><LF>.<CR><LF>",
381: "Enter passphrase",
411: "No such newsgroup",
412: "No newsgroup selected",
420: "Current article number is invalid",
423: "No article found by that number",
430: "No article found by that message ID",
440: "Posting prohibited",
441: "Posting failed",
481: "Authentication failed",
482: "Authentication commands issued out of sequence",
500: "Unknown command",
501: "Syntax error",
502: "Command unavailable",
503: "No list of recommended newsgroups available"
}.get(self.value)
class Response():
__slots__ = 'code', 'message', 'body',
def __init__(self, code: ResponseCode, message: Optional[str]=None, body: Optional[str]=None):
self.code = code
self.message = message or code.message() or "Unknown response"
self.body = body
def __str__(self):
ret = "%d %s" % (self.code.value, self.message)
if self.body:
ret += "\r\n" + self.body
return ret

10
lib/nntp/tiny/server.py Normal file
View file

@ -0,0 +1,10 @@
import enum
class ServerCapability(enum.Flag):
NONE = 0
AUTH = enum.auto()
POST = enum.auto()
class Server():
def __init_(self):
self.capabilities = NNTPServerCapability.NONE

73
lib/nntp/tiny/session.py Normal file
View file

@ -0,0 +1,73 @@
import enum
import socket
import re
from nntp.tiny.server import Server, ServerCapability
from nntp.tiny.response import Response
class SessionState(enum.Flag):
NONE = 0
AUTH_OK = enum.auto()
AUTH_POST = enum.auto()
from nntp.tiny.buffer import LineBuffer, BufferOverflow
class Session():
RE_SPLIT = re.compile(r'\s+')
NNTP_VERSION = 2
NNTP_CAPABILITIES = [
'VERSION %d' % (self.NNTP_VERSION),
'READER',
'HDR',
'NEWNEWS',
'LIST ACTIVE NEWSGROUP OVERVIEW.FMT SUBSCRIPTIONS',
'OVER MSGID'
]
COMMANDS = {
'capabilities':
}
def __init__(self, server: Server, sock: socket.socket):
self.server: Server = server
self.state: SessionState = SessionState.NONE
self.sock: socket.socket = sock
self.buf: LineBuffer = LineBuffer()
def readline(self):
return self.buf.readline(self.sock)
def print(self, text: str, end: str="\r\n"):
return self.sock.send(bytes(text + end, 'ascii'))
def end(self):
return self.print('.')
def respond(self, code: ResponseCode, message: str=None, body=None):
response = Response(code, message, body)
return self.print(str(response))
def _cmd_capabilities(self, *args):
self.respond(ResponseCode.NNTP_CAPABILITIES_FOLLOW)
if self.state & SessionState.AUTH_POST:
self.print('POST')
if self.state & SessionState.AUTH_OK:
self.print('AUTHUSER INFO')
for item in self.NNTP_CAPABILITIES:
self.print(item)
self.end()
def handle(self):
line = self.readline()
if line == '':
return
command, args = self.RE_SPLIT.split(line.rstrip())