Package: kilter.service
Kilter ServiceKilter is a framework for writingmail filters(known as "milters")
compatible with Sendmail and Postfix MTAs.  Unlike many previous milter implementations in
Python it is not simply bindings to thelibmilterlibrary (originally from the Sendmail
project).  The framework aims to provide Pythonic interfaces for implementing filters,
including leveraging coroutines instead oflibmilter's callback-style interface.Thekilter.servicepackage contains the higher-level, asynchronous framework for writing
mail filters.Sendmail FiltersThe Sendmail filter (milter) API facilitates communication between a Mail Transfer Agent
(MTA) and arbitrary filters running as external services.  These filters can perform
a number of operations on received and outgoing mail, such as: virus scanning; checking
senders' reputations; signing outgoing mail; and verifying signatures of incoming mail.While the protocol was originally for filtering mail through a Sendmail MTA, Postfix has
also reverse engineered the protocol and supports most filters made for Sendmail.libmilterHistorically filters used thelibmilterlibrary supplied by the Sendmail project to handle
all aspects of communication with an MTA.  Filters simply registered callbacks for various
events then started the library's main loop. This approach makes implementing simple filters
in C easy for users, but makes writing "Pythonic" filters difficult, especially when a user
wishes to make use of async/await features.Use oflibmilterto implement filters is almost universal as it is a black-box; the
on-the-wire protocol used is undocumented and subject to change between versions, which
makes writing a third-party parser difficult.UsageTo write filters, create a coroutine function that conforms toFilter.  This function
takes aSessionobject as its only argument.Sessioninstances have several awaitable methods corresponding to SMTP commands
(i.e.MAIL,RCPT) and instances ofHeadersAccessorandBodyAccessorwhich have
awaitable methods and are themselves asynchronous iterators.  The various methods await
particular messages from an MTA and may return appropriate values from them.  The
asynchronous iterators yield repeating messages likekilter.protocol.Headerandkilter.protocol.Body.ExamplesThe following is a contrived example showing a filter that rejects messages sent by
a particular user:fromkilter.serviceimportSessionfromkilter.protocolimportReject,Accept# This corncob doesn't know when to stop; block himBLOCK=b"the.black.knight@example.com"asyncdefreject_black_knight(session:Session)->Reject|Accept:if(awaitsession.envelope_from())==BLOCK:returnReject()asyncwithsession.headersasheaders:asyncforheaderinheaders:ifheader.name=="From"andheader.value==BLOCK:returnReject()returnAccept()The following two examples show two implementations of a filter that strips headers starting
with "X-".  They demonstrate the two methods of collecting headers, then later modifying
them during the post phase.fromkilter.serviceimportSessionfromkilter.protocolimportAcceptasyncdefstrip_x_headers(session:Session)->Accept:remove=[]# iterate over headers as they arrive and select ones for later removalasyncwithsession.headersasheaders:asyncforheaderinheaders:ifheader.name.startswith("X-"):remove.append(header)# remove the selected headers during the post phaseforheaderinremove:awaitsession.headers.remove(header)fromkilter.serviceimportSessionfromkilter.protocolimportAcceptasyncdefstrip_x_headers(session:Session)->Accept:# collect the headers firstawaitsession.headers.collect()# iterate over collected headers during the post phase, removing the unwanted onesasyncwithsession.headersasheaders:asyncforheaderinheaders:ifheader.name.startswith("X-"):awaitsession.headers.remove(header)