Library
Module
Module type
Parameter
Class
Class type
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.
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.
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).
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.
module Sigs : sig ... end
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
).
val empty : ctx
empty
is an empty context.
val 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).
with_ip v ctx
adds into the given ctx
the IP address of the sender.
val domain : ctx -> [ `raw ] Domain_name.t option
domain ctx
returns the domain-name of the sender if it exists.
module Macro : sig ... end
module Term : sig ... end
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.
val 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
.
val 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.
val 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.
val inc : [ `raw ] Domain_name.t -> mechanism
The include
mechanism triggers a recursive evaluation of check
:
ctx
check
with the produced domain-name (IP and sender arguments remain the same)include
mechanism is used (see qualifier
)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.
val 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.
val v4 : Ipaddr.V4.Prefix.t -> mechanism
This mechanism test whether the given IP from the given ctx
is contained within a given IPv4 network.
val v6 : Ipaddr.V6.Prefix.t -> mechanism
This mechanism test whether the given IP from the given ctx
is contained within a given IPv: network.
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.
record ms []
returns a record which can be serialized into the zone file of a specific domain-name as the sender policy.
val record_to_string : record -> string
record_to_string v
returns the serialized version of the record to be able to save it into the zone file of a domain-name as the sender policy.
record_of_string ~ctx str
tries to parse and expand macro of the given string which should come from the TXT record of a domain-name as the sender policy of this domain-name.
val pp : record Fmt.t
val pp_res : res Fmt.t
val get :
ctx:ctx ->
't Sigs.state ->
'dns ->
(module Sigs.DNS with type backend = 't and type t = 'dns) ->
(([ res | `Record of record ], [> `Msg of string ]) Stdlib.result, 't)
Sigs.io
get ~ctx scheduler dns (module DNS)
tries to get the sender policy of the domain-name given by ctx
. It requires a scheduler
which allows the high-kind polymorphism over a monadic scheduler and a DNS implementation to request the sender policy.
val check :
ctx:ctx ->
't Sigs.state ->
'dns ->
(module Sigs.DNS with type backend = 't and type t = 'dns) ->
[ res | `Record of record ] ->
(res, 't) Sigs.io
check ~ctx scheduler dns (module DNS)
tries to check the sender policy with the given ctx
. It returns the result of this check.
val to_field :
ctx:ctx ->
?receiver:Emile.domain ->
res ->
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.
type extracted = spf list
and spf = {
result : [ `None
| `Neutral
| `Pass
| `Fail
| `Softfail
| `Temperror
| `Permerror ];
receiver : Emile.domain option;
sender : Emile.mailbox option;
ip : Ipaddr.t option;
ctx : ctx;
}
val pp_spf : spf Fmt.t
val extract_received_spf :
?newline:newline ->
'flow ->
't Sigs.state ->
(module Sigs.FLOW with type backend = 't and type flow = 'flow) ->
((extracted, [> `Msg of string ]) Stdlib.result, 't) Sigs.io
extract_received_spf ?newline flow scheduler (module Flow)
tries to recognized SPF fields values from the given email represented as a flow
.
/
val field_received_spf : Mrmime.Field_name.t
val parse_received_spf_field_value :
Unstrctrd.t ->
(spf, [> `Msg of string ]) Stdlib.result