kilter.service.session module

Sessions are the kernel of a filter, providing it with an async API to access messages

exception Aborted[source]

Bases: BaseException

An exception for aborting filters on receipt of an Abort message

class Filter(*args, **kwargs)[source]

Bases: Protocol

Filters are callables that accept a Session and return a response

class Phase(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]

Bases: int, Enum

Session phases indicate what messages to expect and are impacted by received messages

Users should not generally need to use these values, however an understanding of the state-flow they represent is useful for understanding some error exception raised by Session methods.

CONNECT = 1

This phase is the starting phase of a session, during which a HELO/EHLO message may be awaited with Session.helo().

MAIL = 2

This phase is entered after HELO/EHLO, during which a MAIL message may be awaited with Session.envelope_from(). The Session.extension() method may also be used to get the raw MAIL command with any extension arguments, or any other extension commands that the MTA does not support (if the MTA supports passing these commands to a filter).

ENVELOPE = 3

This phase is entered after MAIL, during which any RCPT commands may be awaited with Session.envelope_recipients(). The Session.extension() method may also be used to get the raw RCPT command with any extension arguments, or any other extension commands that the MTA does not support (if the MTA supports passing these commands to a filter).

HEADERS = 4

This phase is entered after a DATA command, while message headers are processed. Headers may be iterated as they arrive, or be collected for later through the Session.headers object.

BODY = 5

This phase is entered after a message’s headers have been processed. The raw message body may be iterated over in chunks through the Session.body object.

POST = 6

This phase is entered once a message’s body has been completed (or skipped). During this phase the message editing methods of a Session object or the Session.headers and Session.body objects may be used.

class Position(subject: Union[Header, Literal['start'], Literal['end']])[source]

Bases: object

A base class for Before and After, this class is not intended to be used directly

subject: Union[Header, Literal['start'], Literal['end']]
class Before(subject: Header)[source]

Bases: Position

Indicates a relative position preceding a subject Header in a header list

See HeadersAccessor.insert.

subject: Header
class After(subject: Header)[source]

Bases: Position

Indicates a relative position following a subject Header in a header list

See HeadersAccessor.insert.

subject: Header
START = Position(subject='start')

Indicates the start of a header list, before the first (current) header

END = Position(subject='end')

Indicates the end of a header list, after the last (current) header

class Session(connmsg: Connect, sender: AsyncGenerator[None, kilter.service.session.EditMessage], broadcast: Optional[Broadcast[kilter.service.session.EventMessage]] = None)[source]

Bases: object

The kernel of a filter, providing an API for filters to access messages from an MTA

host: str

A hostname from a reverse address lookup performed when a client connects

If no name is found this value defaults to the standard presentation format for Session.address surrounded by “[” and “]”, e.g. “[192.0.2.100]”

address: ipaddress.IPv4Address | ipaddress.IPv6Address | pathlib.Path | None

The address of the connected client, or None if unknown

port: int

The port of the connected client if applicable, or 0 otherwise

macros: dict[str, str]

A mapping of string replacements sent by the MTA

See smfi_getsymval from libmilter for more information.

Warning

The current implementation is very naïve and does not behave exactly like libmilter, nor is it very robust. It will definitely change in the future.

headers: HeadersAccessor

A HeadersAccessor object for accessing and modifying the message header fields

body: BodyAccessor

A BodyAccessor object for accessing and modifying the message body

async deliver(message: kilter.service.session.EventMessage) type[kilter.protocol.messages.Continue] | type[kilter.protocol.messages.Skip][source]

Deliver a message (or its contents) to a task waiting for it

async helo() str[source]

Wait for a HELO/EHLO message and return the client’s claimed hostname

async envelope_from() str[source]

Wait for a MAIL command message and return the sender identity

Note that if extensions arguments are wanted, users should use Session.extension() instead with a name of "MAIL".

async envelope_recipients() AsyncIterator[str][source]

Wait for RCPT command messages and iteratively yield the recipients’ identities

Note that if extensions arguments are wanted, users should use Session.extension() instead with a name of "RCPT".

async extension(name: str) memoryview[source]

Wait for the named command extension and return the raw command for processing

async change_sender(sender: str, args: str = '') None[source]

Move onto the Phase.POST phase and instruct the MTA to change the sender address

async add_recipient(recipient: str, args: str = '') None[source]

Move onto the Phase.POST phase and instruct the MTA to add a new recipient address

async remove_recipient(recipient: str) None[source]

Move onto the Phase.POST phase and instruct the MTA to remove a recipient address

class HeadersAccessor(session: Session, sender: AsyncGenerator[None, kilter.service.session.EditMessage])[source]

Bases: AsyncContextManager[HeaderIterator]

A class that allows access and modification of the message headers sent from an MTA

To access headers (which are only available iteratively), use an instance as an asynchronous context manager; a HeaderIterator is returned when the context is entered.

async collect() None[source]

Collect all headers without producing an iterator

Calling this method before the Phase.BODY phase allows later processing of headers (after the HEADER phase) without the need for an empty loop.

async delete(header: Header) None[source]

Move onto the Phase.POST phase and Instruct the MTA to delete the given header

async update(header: Header, value: bytes) None[source]

Move onto the Phase.POST phase and Instruct the MTA to modify the value of a header

async insert(header: Header, position: Position) None[source]

Move onto the Phase.POST phase and instruct the MTA to insert a new header

The header is inserted at START, END, or a relative position with Before and After; for example Before(Header("To", "test@example.com")).

class HeaderIterator(aiter: AsyncGenerator[Header, None])[source]

Bases: AsyncGenerator[Header, None]

Iterator for headers obtained by using a HeadersAccessor as a context manager

async asend(value: None = None) Header[source]

Send a value into the asynchronous generator. Return next yielded value or raise StopAsyncIteration.

async athrow(e: type[BaseException] | BaseException, m: object = None, t: types.TracebackType | None = None, /) Header[source]

Raise an exception in the asynchronous generator. Return next yielded value or raise StopAsyncIteration.

async aclose() None[source]

Raise GeneratorExit inside coroutine.

async restrict(*names: str) AsyncIterator[Header][source]

Return an asynchronous generator that filters headers by name

class BodyAccessor(session: Session, sender: AsyncGenerator[None, kilter.service.session.EditMessage])[source]

Bases: AsyncContextManager[AsyncIterator[memoryview]]

A class that allows access and modification of the message body sent from an MTA

To access chunks of a body (which are only available iteratively), use an instance as an asynchronous context manager; an asynchronous iterator is returned when the context is entered.

async write(chunk: bytes) None[source]

Request that chunks of a new message body are sent to the MTA

This method should not be called from within the scope created by using it’s instance as an async context (async with); doing so may cause a warning to be issued and the rest of the message body to be skipped.