package uspf

  1. Overview
  2. Docs

Module UspfSource

(Un)Sender Policy Framework.

uSPF is a framework to check the identity of the email's sender. When an email passes through an SMTP server, some informations are available such as the source of the email, the IP address (because the sender must initiate a TCP/IP connexion).

From these informations and via uSPF (and DNS records), we are able to authorize the given email or not. Indeed, the email submission process requires an identity with the SMTP MAILFROM command. At this stage, we are able to check if the domain name given by MAILFROM and the current IP address of the sender match!

The domain-name used by MAILFROM should have some DNS records which describe which IP address is allowed to send an email via the MAILFROM's identity. uSPF will check that and it will try to find a match. In any results - if uSPF fails or not - the SMTP server will put the result of such check into the given email.

Finally, it permits to check, at any step of the submission, the identity of the sender. However, it does not ensure a high level of securities when uSPF should be use with DKIM/DMARC to ensure some others aspects such as the integrity of the given email.

How to use uSPF.

uSPF requires some meta informations such as the MAILFROM identity and the IP address of the sender. The user can create a ctx and fill it with these information:

  let ctx =
    Uspf.empty |> Uspf.with_sender (`MAILFROM path) |> Uspf.with_ip ipaddr

From this ctx, then the user is able to check the identity of the sender via a DNS implementation. The user must get the SPF DNS record, analyze it and use it then with the ctx:

  let res =
    Uspf.get ctx sched dns (module DNS)
    >>= Uspf.check ctx sched dns (module DNS)

From the result, the user is able to generate an header field. It optional to give your identity (your domain) to be exhaustive about meta information on the field value:

  let field_name, value = Uspf.to_field ~ctx ?receiver res

The value is well-formed for the incoming email. You just need to prepend the field before the email.

Reproductibility.

The API provides a possibility to extract SPF results from an incoming email and regenerate the ctx from them. By this way, locally, you can reproduce the process above. By this way, you are able to reproduce the written result and check if it still is valid.

Indeed, due to the DNS record requirement to check the identity of the sender, it possible that meta informations from the given email are obsoletes (for any reasons).

As a server.

uSPF allows the end-user to craft its own record and publish it then into its primary/secondary DNS server. Multiple values exists such as:

They permits to describe via OCaml the SPF record. It can be serialized to a simple string then and the user can save it into its own primary/secondary DNS server.

Sourcetype ctx

The type for contexts. It's a heterogeneous map of values to help uSPF to validate the sender. It requires the MAILFROM parameter given by the SMTP protocol (which can be filled via with_sender) and/or the IP address of the sender (which can be filled via with_ip).

Sourceval empty : ctx

empty is an empty context.

Sourceval with_sender : [ `HELO of [ `raw ] Domain_name.t | `MAILFROM of Colombe.Path.t ] -> ctx -> ctx

with_sender v ctx adds into the given ctx the sender of the incoming email (its simple domain name or the complete email address).

Sourceval with_ip : Ipaddr.t -> ctx -> ctx

with_ip v ctx adds into the given ctx the IP address of the sender.

Sourceval domain : ctx -> [ `raw ] Domain_name.t option

domain ctx returns the domain-name of the sender if it exists.

Sourceval origin : ctx -> [ `HELO | `MAILFROM ] option

origin ctx returns the origin of the sender (from the HELO SMTP command or the MAILFROM command).

Sourceval merge : ctx -> ctx -> ctx option

merge ctx0 ctx1 merges the given contexts and ensure that they are partially equal.

Sourcemodule Macro : sig ... end
Sourcemodule Term : sig ... end
Sourcetype mechanism

The type of mechanisms.

A mechanism permits to design and identify a set of IP addresses as being permitted or not permitted to use the domain for sending mail.

Sourceval a : ?cidr_v4:int -> ?cidr_v6:int -> [ `raw ] Domain_name.t -> mechanism

This mechanism matches if the sender's IP address is one of the domain-name's IP addresses. For clarity, this means the a mechanism also matches AAAA records.

An address lookup is done on the domain-name using the type of lookup (A or AAAA) appropriate for the connection type. The IP is compared to the returned address(es). If any address matches, the mechanism matches.

A Classless Inter-Domain Routing can be applied to returned address(es) (IPv4 or IPv6) to compare with the sender's IP address. For instance, a=10.0.0.42/32 matches only 10.0.0.42 as the sender's IP address but a=10.0.0.42/24 matches any 10.0.0.* addresses. By default, cidr_v4 = 32 and cidr_v6 = 128.

Sourceval all : mechanism

The all mechanism is a test that always matches. It is used as the rightmost mechanism (the last mechanism) in a record to provide an explicit default. For example v=spf1 a mx -all.

Mechanisms after all will never be tested.

Sourceval exists : [ `raw ] Domain_name.t -> mechanism

This mechanism is used to construct an arbitrary domain name that is used for a DNS A record query. It allows for complicated schemes involving arbitrary parts on the mail envelope to determine what is permitted.

Sourceval inc : [ `raw ] Domain_name.t -> mechanism

The include mechanism triggers a recursive evaluation of check:

  1. The macro is expanded according to the given ctx
  2. We re-execute check with the produced domain-name (IP and sender arguments remain the same)
  3. The recursive evaluation returns match, not-match or an error.
  4. If it returns match, then the appropriate result for the include mechanism is used (see qualifier)
  5. It it returns not-match or an error, the check process tests the next mechanism.

Note: for instance, if the domain-name has -all, include does not strictly terminates the processus. It fails and let check to process the next mechanism.

Sourceval mx : ?cidr_v4:int -> ?cidr_v6:int -> [ `raw ] Domain_name.t -> mechanism

This mechanims matches if the sender's IP is one of the MX hosts for a domain-name. A domain-name should have a MX record which is an IP address. If this IP address is the same as the given IP address into the given ctx, we consider that the sender matches.

Note: if the domain-name has no MX record, check does not apply the implicit MX rules by querying for an A or AAAA record for the same name.

This mechanism test whether the given IP from the given ctx is contained within a given IPv4 network.

This mechanism test whether the given IP from the given ctx is contained within a given IPv: network.

Sourcetype modifier

The type of modifiers.

They are not available because they mostly provide additional information which are not needed for check. By this way, it's not needed to let the user to define some when they are not effective on the user's sender policy.

Sourcetype qualifier =
  1. | Pass
  2. | Fail
  3. | Softfail
  4. | Neutral
    (*

    The type of qualifiers.

    A qualifier specifies what the mechanism returns when it matches or not:

    • + returns pass if the mechanism matches
    • - returns fail if the mechanism matches
    • ~ returns softfail if the mechanism matches
    • ? returns neutral if the mechanism matches
    *)

pass m specifies the qualifier of the given mechanism m. If the mechanism matches from the given ctx, check returns `Pass. Otherwise, check tries the next mechanism.

fail m specifies the qualifier of the given mechanism m. If the mechanism matches from the given ctx, check tries the next mechanism (as it considers the current one as a failure). If the mechanism is the last one, check returns `Fail so.

Sourceval softfail : mechanism -> qualifier * mechanism

softfail m specifies the qualifier of the given mechanism m. If the mechanism matches from the given ctx, check tries the next mechanism (as it considers the current one as a soft failure). If the mechanism is the last one, check returns `Softfail so.

neutral m specifies the qualifier of the given mechanism m. Regardless the result of the mechanism (if it matches or not), check tries the next mechanism. If the mechanism is the last one, check returns `Neutral so.

Sourcemodule Record : sig ... end
Sourcemodule Result : sig ... end
Sourcetype error = [
  1. | `Msg of string
  2. | `No_data of [ `raw ] Domain_name.t * Dns.Soa.t
  3. | `No_domain of [ `raw ] Domain_name.t * Dns.Soa.t
]
Sourcetype 'a response = ('a, error) result
Sourcetype 'a record = 'a Dns.Rr_map.key
Sourcetype 'a choose = {
  1. none : (unit -> 'a t) option;
  2. neutral : (unit -> 'a t) option;
  3. pass : (mechanism -> 'a t) option;
  4. fail : (unit -> 'a t) option;
  5. softfail : (unit -> 'a t) option;
  6. temperror : (unit -> 'a t) option;
  7. permerror : (unit -> 'a t) option;
  8. fn : unit -> 'a t;
}
Sourceand 'a t =
  1. | Return : 'a -> 'a t
  2. | Request : _ Domain_name.t * 'a record * ('a response -> 'b t) -> 'b t
  3. | Tries : (unit -> unit t) list -> unit t
  4. | Map : 'a t * ('a -> 'b) -> 'b t
  5. | Choose_on : 'a choose -> 'a t
Sourceexception Result of Result.t
Sourceval terminate : Result.t -> 'a
Sourceval get_and_check : ctx -> unit t
Sourceval to_field : ctx:ctx -> ?receiver:Emile.domain -> Result.t -> Mrmime.Field_name.t * Unstrctrd.t

to_field ~ctx ?received v serializes as an email field the result of the sender policy check according to the given ctx. The user is able to prepend then its email with this field.

Sourcemodule Extract : sig ... end
Sourcemodule Encoder : sig ... end
Sourceval field_received_spf : Mrmime.Field_name.t
OCaml

Innovation. Community. Security.