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;
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.TryLookupText(Term.domain);
58 if (TXT is null)
59 TermStrings = null;
60 else
61 {
62 foreach (string Row in TXT)
63 {
64 s = Row.Trim();
65
66 if (s.Length > 1 && s[0] == '"' && s[s.Length - 1] == '"')
67 s = s.Substring(1, s.Length - 2);
68
69 if (!s.StartsWith("v=spf1"))
70 continue;
71
72 if (!(TermStrings is null))
73 return new KeyValuePair<SpfResult, string>(SpfResult.PermanentError, "Multiple SPF records found for " + Term.domain + ".");
74
75 TermStrings = s.Substring(6).Trim().Split(space, StringSplitOptions.RemoveEmptyEntries);
76 }
77 }
78 }
79 catch (Exception)
80 {
81 TermStrings = null;
82 }
83
84 if (TermStrings is null)
85 {
86 if (!(SpfExpressions is null))
87 {
88 foreach (SpfExpression Expression in SpfExpressions)
89 {
90 if (Expression.IsApplicable(Term.domain))
91 {
92 if (Expression.Spf.StartsWith("v=spf1"))
93 {
94 TermStrings = Expression.Spf.Substring(6).Trim().Split(space, StringSplitOptions.RemoveEmptyEntries);
95 break;
96 }
97 }
98 }
99 }
100
101 if (TermStrings is null)
102 return new KeyValuePair<SpfResult, string>(SpfResult.None, "No SPF records found " + Term.domain + ".");
103 }
104
105 // Syntax evaluation first, §4.6
106
107 int c = TermStrings.Length;
108 LinkedList<Mechanism> Mechanisms = new LinkedList<Mechanism>();
109 Redirect Redirect = null;
110 int i;
111
112 try
113 {
114 for (i = 0; i < c; i++)
115 {
116 SpfQualifier Qualifier;
117
118 Term.Reset(TermStrings[i]);
119 Term.SkipWhiteSpace();
120
121 switch (Term.PeekNextChar())
122 {
123 case '+':
124 Term.pos++;
125 Qualifier = SpfQualifier.Pass;
126 break;
127
128 case '-':
129 Term.pos++;
130 Qualifier = SpfQualifier.Fail;
131 break;
132
133 case '~':
134 Term.pos++;
135 Qualifier = SpfQualifier.SoftFail;
136 break;
137
138 case '?':
139 Term.pos++;
140 Qualifier = SpfQualifier.Neutral;
141 break;
142
143 default:
144 Qualifier = SpfQualifier.Pass;
145 break;
146 }
147
148 switch (Term.NextLabel().ToLower())
149 {
150 case "all":
151 Mechanisms.AddLast(new All(Term, Qualifier));
152 break;
153
154 case "include":
155 Mechanisms.AddLast(new Include(Term, Qualifier, SpfExpressions));
156 break;
157
158 case "a":
159 Mechanisms.AddLast(new A(Term, Qualifier));
160 break;
161
162 case "mx":
163 Mechanisms.AddLast(new Mx(Term, Qualifier));
164 break;
165
166 case "ptr":
167 Mechanisms.AddLast(new Ptr(Term, Qualifier));
168 break;
169
170 case "ip4":
171 Mechanisms.AddLast(new Ip4(Term, Qualifier));
172 break;
173
174 case "ip6":
175 Mechanisms.AddLast(new Ip6(Term, Qualifier));
176 break;
177
178 case "exists":
179 Mechanisms.AddLast(new Exists(Term, Qualifier));
180 break;
181
182 case "redirect":
183 if (!(Redirect is null))
184 return new KeyValuePair<SpfResult, string>(SpfResult.PermanentError, "Multiple redirect modifiers foundin SPF record.");
185
186 Redirect = new Redirect(Term, Qualifier);
187 break;
188
189 case "exp":
190 if (!(Explanation is null))
191 return new KeyValuePair<SpfResult, string>(SpfResult.PermanentError, "Multiple exp modifiers foundin SPF record.");
192
193 Explanation = new Exp(Term, Qualifier);
194 break;
195
196 default:
197 throw new Exception("Syntax error.");
198 }
199 }
200
201 foreach (Mechanism Mechanism in Mechanisms)
202 {
203 await Mechanism.Expand();
204
205 SpfResult Result = await Mechanism.Matches();
206
207 switch (Result)
208 {
209 case SpfResult.Pass:
210 switch (Mechanism.Qualifier)
211 {
212 case SpfQualifier.Pass: return new KeyValuePair<SpfResult, string>(SpfResult.Pass, null);
213 case SpfQualifier.Fail: return new KeyValuePair<SpfResult, string>(SpfResult.Fail, Explanation is null ? null : await Explanation.Evaluate());
214 case SpfQualifier.Neutral: return new KeyValuePair<SpfResult, string>(SpfResult.Neutral, null);
215 case SpfQualifier.SoftFail: return new KeyValuePair<SpfResult, string>(SpfResult.SoftFail, Explanation is null ? null : await Explanation.Evaluate());
216 }
217 break;
218
219 case SpfResult.TemporaryError:
220 return new KeyValuePair<SpfResult, string>(SpfResult.TemporaryError, Explanation is null ? null : await Explanation.Evaluate());
221
222 case SpfResult.None:
223 case SpfResult.PermanentError:
224 return new KeyValuePair<SpfResult, string>(SpfResult.PermanentError, Explanation is null ? null : await Explanation.Evaluate());
225 }
226 }
227
228 if (!(Redirect is null))
229 {
230 await Redirect.Expand();
231
232 string Bak = Term.domain;
233 Term.domain = Redirect.Domain;
234 try
235 {
236 KeyValuePair<SpfResult, string> Result = await SpfResolver.CheckHost(Term, SpfExpressions);
237
238 if (Result.Key == SpfResult.None)
239 return new KeyValuePair<SpfResult, string>(SpfResult.PermanentError, Explanation is null ? null : await Explanation.Evaluate());
240 else if (Result.Key != SpfResult.Pass && Result.Key != SpfResult.Neutral &&
241 string.IsNullOrEmpty(Result.Value))
242 {
243 return new KeyValuePair<SpfResult, string>(Result.Key, Explanation is null ? null : await Explanation.Evaluate());
244 }
245 else
246 return Result;
247 }
248 finally
249 {
250 Term.domain = Bak;
251 }
252
253 }
254 }
255 catch (Exception ex)
256 {
257 return new KeyValuePair<SpfResult, string>(SpfResult.PermanentError, "Unable to evaluate SPF record: " + FirstRow(ex.Message));
258 }
259
260 return new KeyValuePair<SpfResult, string>(SpfResult.Neutral, null);
261 }
262
263 private static string FirstRow(string s)
264 {
265 int i = s.IndexOfAny(CRLF);
266 if (i < 0)
267 return s;
268 else
269 return s.Substring(0, i);
270 }
271
272 private static readonly char[] space = new char[] { ' ' };
273 private static readonly char[] CRLF = new char[] { '\r', '\n' };
274 }
275}
DNS resolver, as defined in:
Definition: DnsResolver.cs:32
static Task< string[]> TryLookupText(string Name)
Tries to look up text (TXT) records for a name.
Definition: DnsResolver.cs:954
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