chapps.switchboard module

Communication handlers

This module encapsulates the particular logic of:

  1. receiving data payloads from Postfix, and then

  2. sending back an appropriately-formatted response once the policy manager has had a chance to weigh in on the payload contents

Classes defined here exist mainly to be factories which return the main-loop closure for asyncio.

TODO: RequestHandler should be a subclass of CascadingPolicyHandler which simply only ever has one policy within it. This is to avoid maintaining two nearly-identical code-paths. Running only one policy is obviously a special case of running many.

class chapps.switchboard.RequestHandler[source]

Bases: object

Deprecated base class for wrapping policy managers in an event loop

This workhorse base class generalizes creating an event loop around an email policy. It has been superseded by CascadingPolicyHandler, which expands on the logic of this (original) class by allowing any number of policies to be applied in order.

Instance attributes:

policy

the EmailPolicy manager instance

config

a reference to the CHAPPSConfig stored on the policy instance.

pprclass

a reference to the particular kind of PostfixPolicyRequest to instantiate

__init__(policy, *, pprclass=<class 'chapps.util.PostfixPolicyRequest'>)[source]

Setup a Postfix policy request handler

Parameters

Note

Unlike other class families within CHAPPS, the handlers in this module do not accept config-override arguments. They obtain their references to the config from their attached policy managers.

property listen_address
property listen_port
async_policy_handler()[source]

Coroutine factory

Returns

a coroutine which handles requests by Postfix to the policy

The policy handler closure is meant to do all the weird grunt work associated with talking to some other application over a socket. It listens, and it minimally processes the payload it receives in order to pass something sane to the rest of the library.

Part of this is accomplished through use of the utility class PostfixPolicyRequest and its subclass(es). The utility class turns the payload into something that the rest of the code can work with easily.

class chapps.switchboard.CascadingPolicyHandler[source]

Bases: object

Second-generation handler class which cascades multiple yes/no policies

This class started out nearly identical to RequestHandler, but as the software has moved on, so has this handler, which is the main one in general use.

This handler accepts a list of policy manager instances, all of which should produce True/False results. The handler applies each policy to each request, and passes those which pass both, or returns the message from the policy which failed. Once a policy has failed, no further policies are consulted.

Instance attributes:

policies

a list of EmailPolicy objects

pprclass

the class of PostfixPolicyRequest to instantiate from the Postfix request payload

config

a reference to the CHAPPSConfig stored on the first policy in the list

listen_address

the IP address to bind to; see listen_address()

listen_port

the port to listen on; see listen_port()

__init__(policies=[], *, pprclass=<class 'chapps.util.PostfixPolicyRequest'>)[source]
property listen_address
property listen_port
async_policy_handler()[source]

Coroutine factory

Returns

a coroutine which handles requests according to the policies, in order

class chapps.switchboard.OutboundMultipolicyHandler[source]

Bases: chapps.switchboard.CascadingPolicyHandler

Convenience subclass for combining outbound P/F policies

Could be thought of as a concrete subclass of CascadingPolicyHandler, but meant more as a convenience.

__init__(policies=[], *, pprclass=<class 'chapps.outbound.OutboundPPR'>)[source]

Setup an OutboundMultipolicyHandler

Parameters

If none are provided, default-configured instances of SenderDomainAuthPolicy and OutboundQuotaPolicy are used, in that order.

class chapps.switchboard.OutboundQuotaHandler[source]

Bases: chapps.switchboard.RequestHandler

Convenience class for wrapping OutboundQuotaPolicy

__init__(policy=None)[source]

Setup an OutboundQuotaHandler

Parameters

policy (chapps.policy.OutboundQuotaPolicy) – an instance of OutboundQuotaPolicy

class chapps.switchboard.GreylistingHandler[source]

Bases: chapps.switchboard.RequestHandler

Convenience class for wrapping GreylistingPolicy

__init__(policy=None)[source]

Setup a GreylistingHandler

Parameters

policy (chapps.policy.GreylistingPolicy) – an instance of GreylistingPolicy

class chapps.switchboard.SenderDomainAuthHandler[source]

Bases: chapps.switchboard.RequestHandler

Convenience class for wrapping SenderDomainAuthPolicy

__init__(policy=None)[source]

Setup a SenderDomainAuthHandler

Parameters

policy (chapps.policy.SenderDomainAuthPolicy) – an instance of SenderDomainAuthPolicy

class chapps.switchboard.SPFEnforcementHandler[source]

Bases: chapps.switchboard.RequestHandler

Special handler class for SPFEnforcementPolicy

This one came along last and forced a reconsideration of how all this worked, because it produces more than two possible states as output. The plan is to retrofit all the older policies so that they also can use an action-translation layer, but that will also require some adjustment of the cascading handler.

Note

This class will not be defined if the relevant SPF libraries could not be loaded. They may be installed via pip using the extras mechanism: pip install chapps[SPF]

__init__(policy=None)[source]

Set up an SPFEnforcementHandler

Parameters

policy (chapps.spf_policy.SPFEnforcementPolicy) – an instance of SPFEnforcementPolicy

async_policy_handler()[source]

Returns a coroutine which handles results according to the configuration

The policy being enforced is stored in the SPF-related TXT record on the sender’s domain. The local configuration of this policy amounts to instructions about responses to different outcomes of the SPF check, along with what IP address and port to listen on.

This policy handler is different from others in that, because it does not expect a PASS/FAIL response, it simply wraps the return value of approve_policy_request() in a Postfix response packet, and sends it. Rather than refer to pre-configured acceptance and rejection messages, it expects the approval routine to send a string which can be interpreted by Postfix as a command.

TODO: In order to be able to cascade through this kind of policy, it is going to have to return a first-class object which can be annotated as being a pass or a fail, so that a cascading handler can decide whether to continue. That object’s __str__() method will need to return the Postfix command.