1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
//! Find where to report abuse for an IP address or a domain name.
//!
//! The contact lives in a different place for each kind of target, and the answers
//! are not interchangeable. The registrar of a domain can suspend the name. The
//! network that holds an IP address can take the host off the air. The operator of
//! the domain runs the service. A report goes to the one that can act on it, so this
//! crate returns every contact it finds with the scope it covers, and leaves the
//! choice to you.
//!
//! # Sources
//!
//! | Source | Target | What it gives |
//! | --- | --- | --- |
//! | RDAP | IP, domain | The contact the registry publishes |
//! | Abusix | IP | The contact for the network, over DNS |
//! | abuse.net | domain | The contact the operator registered, over DNS |
//! | RFC 2142 | domain | `abuse@` at the domain, a guess |
//!
//! # Reading a result
//!
//! ```
//! use abuse_contact::{Contact, EmailAddress, Scope, Source, rank};
//!
//! let found = vec![
//! Contact {
//! email: EmailAddress::new("abuse@example.com")?,
//! scope: Scope::Domain,
//! source: Source::Rfc2142,
//! },
//! Contact {
//! email: EmailAddress::new("registrar-abuse@example.net")?,
//! scope: Scope::Registrar,
//! source: Source::Rdap { server: "rdap.example.net".to_owned() },
//! },
//! ];
//!
//! // The registry answer comes first. The guess comes last.
//! let ranked = rank(found);
//! assert_eq!(ranked[0].scope, Scope::Registrar);
//!
//! // Pick by what you want to happen, not by what is first.
//! let takedown = ranked.iter().find(|c| c.scope == Scope::Registrar);
//! assert!(takedown.is_some());
//! # Ok::<(), abuse_contact::ValidationError>(())
//! ```
//!
//! # Design
//!
//! Values that go out are checked when you build them. [`DomainName`] refuses a name
//! that no registry can hold, so a lookup that is certain to find nothing never
//! reaches the network.
//!
//! Values that come back are not checked the same way. A change on a registry side
//! must not turn a working lookup into a parse error, so [`rdap::Response`] reads the
//! few fields an abuse lookup needs and ignores the rest.
//!
//! One value that comes back is refused: a registry that hides contact data puts a
//! placeholder such as `DATA REDACTED` in the email field. [`EmailAddress`] rejects
//! it, so the placeholder never reaches a mail queue.
//!
//! A domain takes one request, not two. The registry record names the registrar and
//! carries its abuse address under the registrar entity, so one lookup is enough.
//! [`rdap::Response::related_href`] gives the registrar record for the details the
//! registry leaves out, such as the abuse telephone number.
//!
//! Registries do not agree on where the abuse entity goes, or on whether to publish
//! one. ARIN puts it under the registrant and again at the top level. RIPE, APNIC and
//! LACNIC put it at the top level. APNIC marks the abuse mailbox with `pref` and lists
//! a help desk beside it. registro.br marks one entity both technical and abuse and
//! gives it no address. AFRINIC publishes no abuse entity at all. The reader walks the
//! whole tree, sorts by preference, and returns each address one time.
//!
//! RDAP therefore does not answer everywhere. For AFRINIC space and for registro.br
//! space, DNS is the only source that gives an address. Ask more than one source.
//!
//! # State of this crate
//!
//! The part that decides what an answer means is here and is tested. The part that
//! fetches an answer is not written yet: it needs an HTTP client for RDAP and a
//! resolver for the DNS zones. Until then, fetch with the client you already have and
//! call [`rdap::Response::abuse_contacts`], [`dns::contacts_from_txt`] and [`rank`]
//! on what comes back.
pub use ;
pub use ValidationError;
pub use ;