Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
SpfResolver.cs
1using System;
2using System.Collections.Generic;
3using System.Net;
4using System.Threading.Tasks;
7
8namespace Waher.Security.SPF
9{
15 public static class SpfResolver
16 {
33 public static Task<KeyValuePair<SpfResult, string>> CheckHost(IPAddress Address, string DomainName, string Sender,
34 string HelloDomain, string HostDomain, params SpfExpression[] SpfExpressions)
35 {
36 Term Term = new Term(Sender, DomainName, Address, HelloDomain, HostDomain);
37 return CheckHost(Term, SpfExpressions);
38 }
39
49 internal static async Task<KeyValuePair<SpfResult, string>> CheckHost(Term Term, SpfExpression[] SpfExpressions)
50 {
51 Exp Explanation = null;
52 string[] TermStrings = null;
53 string s;
54
55 try
56 {
57 string[] TXT = await DnsResolver.LookupText(Term.domain);
58
59 foreach (string Row in TXT)
60 {
61 s = Row.Trim();
62
63 if (s.Length > 1 && s[0] == '"' && s[s.Length - 1] == '"')
64 s = s.Substring(1, s.Length - 2);
65
66 if (!s.StartsWith("v=spf1"))
67 continue;
68
69 if (!(TermStrings is null))
70 return new KeyValuePair<SpfResult, string>(SpfResult.PermanentError, "Multiple SPF records found for " + Term.domain + ".");
71
72 TermStrings = s.Substring(6).Trim().Split(space, StringSplitOptions.RemoveEmptyEntries);
73 }
74 }
75 catch (Exception)
76 {
77 TermStrings = null;
78 }
79
80 if (TermStrings is null)
81 {
82 if (!(SpfExpressions is null))
83 {
84 foreach (SpfExpression Expression in SpfExpressions)
85 {
86 if (Expression.IsApplicable(Term.domain))
87 {
88 if (Expression.Spf.StartsWith("v=spf1"))
89 {
90 TermStrings = Expression.Spf.Substring(6).Trim().Split(space, StringSplitOptions.RemoveEmptyEntries);
91 break;
92 }
93 }
94 }
95 }
96
97 if (TermStrings is null)
98 return new KeyValuePair<SpfResult, string>(SpfResult.None, "No SPF records found " + Term.domain + ".");
99 }
100
101 // Syntax evaluation first, §4.6
102
103 int c = TermStrings.Length;
104 LinkedList<Mechanism> Mechanisms = new LinkedList<Mechanism>();
105 Redirect Redirect = null;
106 int i;
107
108 try
109 {
110 for (i = 0; i < c; i++)
111 {
112 SpfQualifier Qualifier;
113
114 Term.Reset(TermStrings[i]);
115 Term.SkipWhiteSpace();
116
117 switch (Term.PeekNextChar())
118 {
119 case '+':
120 Term.pos++;
121 Qualifier = SpfQualifier.Pass;
122 break;
123
124 case '-':
125 Term.pos++;
126 Qualifier = SpfQualifier.Fail;
127 break;
128
129 case '~':
130 Term.pos++;
131 Qualifier = SpfQualifier.SoftFail;
132 break;
133
134 case '?':
135 Term.pos++;
136 Qualifier = SpfQualifier.Neutral;
137 break;
138
139 default:
140 Qualifier = SpfQualifier.Pass;
141 break;
142 }
143
144 switch (Term.NextLabel().ToLower())
145 {
146 case "all":
147 Mechanisms.AddLast(new All(Term, Qualifier));
148 break;
149
150 case "include":
151 Mechanisms.AddLast(new Include(Term, Qualifier, SpfExpressions));
152 break;
153
154 case "a":
155 Mechanisms.AddLast(new A(Term, Qualifier));
156 break;
157
158 case "mx":
159 Mechanisms.AddLast(new Mx(Term, Qualifier));
160 break;
161
162 case "ptr":
163 Mechanisms.AddLast(new Ptr(Term, Qualifier));
164 break;
165
166 case "ip4":
167 Mechanisms.AddLast(new Ip4(Term, Qualifier));
168 break;
169
170 case "ip6":
171 Mechanisms.AddLast(new Ip6(Term, Qualifier));
172 break;
173
174 case "exists":
175 Mechanisms.AddLast(new Exists(Term, Qualifier));
176 break;
177
178 case "redirect":
179 if (!(Redirect is null))
180 return new KeyValuePair<SpfResult, string>(SpfResult.PermanentError, "Multiple redirect modifiers foundin SPF record.");
181
182 Redirect = new Redirect(Term, Qualifier);
183 break;
184
185 case "exp":
186 if (!(Explanation is null))
187 return new KeyValuePair<SpfResult, string>(SpfResult.PermanentError, "Multiple exp modifiers foundin SPF record.");
188
189 Explanation = new Exp(Term, Qualifier);
190 break;
191
192 default:
193 throw new Exception("Syntax error.");
194 }
195 }
196
197 foreach (Mechanism Mechanism in Mechanisms)
198 {
199 await Mechanism.Expand();
200
201 SpfResult Result = await Mechanism.Matches();
202
203 switch (Result)
204 {
205 case SpfResult.Pass:
206 switch (Mechanism.Qualifier)
207 {
208 case SpfQualifier.Pass: return new KeyValuePair<SpfResult, string>(SpfResult.Pass, null);
209 case SpfQualifier.Fail: return new KeyValuePair<SpfResult, string>(SpfResult.Fail, Explanation is null ? null : await Explanation.Evaluate());
210 case SpfQualifier.Neutral: return new KeyValuePair<SpfResult, string>(SpfResult.Neutral, null);
211 case SpfQualifier.SoftFail: return new KeyValuePair<SpfResult, string>(SpfResult.SoftFail, Explanation is null ? null : await Explanation.Evaluate());
212 }
213 break;
214
215 case SpfResult.TemporaryError:
216 return new KeyValuePair<SpfResult, string>(SpfResult.TemporaryError, Explanation is null ? null : await Explanation.Evaluate());
217
218 case SpfResult.None:
219 case SpfResult.PermanentError:
220 return new KeyValuePair<SpfResult, string>(SpfResult.PermanentError, Explanation is null ? null : await Explanation.Evaluate());
221 }
222 }
223
224 if (!(Redirect is null))
225 {
226 await Redirect.Expand();
227
228 string Bak = Term.domain;
229 Term.domain = Redirect.Domain;
230 try
231 {
232 KeyValuePair<SpfResult, string> Result = await SpfResolver.CheckHost(Term, SpfExpressions);
233
234 if (Result.Key == SpfResult.None)
235 return new KeyValuePair<SpfResult, string>(SpfResult.PermanentError, Explanation is null ? null : await Explanation.Evaluate());
236 else if (Result.Key != SpfResult.Pass && Result.Key != SpfResult.Neutral &&
237 string.IsNullOrEmpty(Result.Value))
238 {
239 return new KeyValuePair<SpfResult, string>(Result.Key, Explanation is null ? null : await Explanation.Evaluate());
240 }
241 else
242 return Result;
243 }
244 finally
245 {
246 Term.domain = Bak;
247 }
248
249 }
250 }
251 catch (Exception ex)
252 {
253 return new KeyValuePair<SpfResult, string>(SpfResult.PermanentError, "Unable to evaluate SPF record: " + FirstRow(ex.Message));
254 }
255
256 return new KeyValuePair<SpfResult, string>(SpfResult.Neutral, null);
257 }
258
259 private static string FirstRow(string s)
260 {
261 int i = s.IndexOfAny(CRLF);
262 if (i < 0)
263 return s;
264 else
265 return s.Substring(0, i);
266 }
267
268 private static readonly char[] space = new char[] { ' ' };
269 private static readonly char[] CRLF = new char[] { '\r', '\n' };
270 }
271}
DNS resolver, as defined in:
Definition: DnsResolver.cs:32
static async Task< string[]> LookupText(string Name)
Looks up text (TXT) records for a name.
Definition: DnsResolver.cs:745
This mechanism matches if <ip> is one of the <target-name>'s IP addresses.For clarity,...
Definition: A.cs:15
The "all" mechanism is a test that always matches. It is used as the rightmost mechanism in a record ...
Definition: All.cs:12
This mechanism is used to construct an arbitrary domain name that is used for a DNS A record query....
Definition: Exists.cs:15
If check_host() results in a "fail" due to a mechanism match (such as "-all"), and the "exp" modifier...
Definition: Exp.cs:16
async Task< string > Evaluate()
Evaluates the explanation.
Definition: Exp.cs:49
The "include" mechanism triggers a recursive evaluation of check_host().
Definition: Include.cs:11
This mechanisms tests whether <ip> is contained within a given IP4 network.
Definition: Ip4.cs:13
This mechanisms tests whether <ip> is contained within a given IP6 network.
Definition: Ip6.cs:12
override async Task Expand()
Expands any macros in the domain specification.
Abstract base class for SPF Mechanisms.
Definition: Mechanism.cs:11
virtual Task Expand()
Expands any macros in the domain specification.
Definition: Mechanism.cs:41
abstract Task< SpfResult > Matches()
Checks if the mechamism matches the current request.
SpfQualifier Qualifier
Mechanism qualifier
Definition: Mechanism.cs:36
This mechanism tests whether the DNS reverse-mapping for <ip> exists and correctly points to a domain...
Definition: Ptr.cs:16
The "redirect" modifier is intended for consolidating both authorizations and policy into a common se...
Definition: Redirect.cs:14
Contains information about a SPF string.
bool IsApplicable(string Domain)
Checks if the expression is applicable to a given domain.
string Spf
SPF expression.
Resolves a SPF string, as defined in:
Definition: SpfResolver.cs:16
static Task< KeyValuePair< SpfResult, string > > CheckHost(IPAddress Address, string DomainName, string Sender, string HelloDomain, string HostDomain, params SpfExpression[] SpfExpressions)
Fetches SPF records, parses them, and evaluates them to determine whether a particular host is or is ...
Definition: SpfResolver.cs:33
void Reset(string String)
Resets the string representation of the term.
Definition: Term.cs:75
SpfQualifier
SPF Mechanism qualifier
Definition: Term.cs:12
SpfResult
Result of a SPF (Sender Policy Framework) evaluation.
Definition: SpfResult.cs:11