/// <summary> /// Fetches SPF records, parses them, and /// evaluates them to determine whether a particular host is or is not /// permitted to send mail with a given identity. /// </summary> /// <param name="spfStatement">Information about current query.</param> /// <param name="spfExpressions">SPF Expressions that can be used, in case a domain lacks SPF records in the DNS.</param> /// <returns>Result of SPF evaluation, together with an optional explanation string, /// if one exists, and if the result indicates a failure.</returns> internal static async Task <KeyValuePair <SpfResult, string> > CheckHost(SpfStatement spfStatement, SpfExpression[] spfExpressions) { Explanation explanation = null; string[] spfStatementStrings = null; string s; try { string[] txt = await DnsResolver.LookupText(spfStatement.Domain); foreach (string row in txt) { s = row.Trim(); if (s.Length > 1 && s[0] == '"' && s[s.Length - 1] == '"') { s = s.Substring(1, s.Length - 2); } if (!s.StartsWith("v=spf1")) { continue; } if (!(spfStatementStrings is null)) { return(new KeyValuePair <SpfResult, string>(SpfResult.PermanentError, "Multiple SPF records found for " + spfStatement.Domain + ".")); } spfStatementStrings = s.Substring(6).Trim().Split(Space, StringSplitOptions.RemoveEmptyEntries); } } catch (Exception) { spfStatementStrings = null; } if (spfStatementStrings is null) { if (!(spfExpressions is null)) { foreach (SpfExpression expression in spfExpressions) { if (expression.IsApplicable(spfStatement.Domain)) { if (expression.Spf.StartsWith("v=spf1")) { spfStatementStrings = expression.Spf.Substring(6).Trim() .Split(Space, StringSplitOptions.RemoveEmptyEntries); break; } } } } if (spfStatementStrings is null) { return(new KeyValuePair <SpfResult, string>(SpfResult.None, "No SPF records found " + spfStatement.Domain + ".")); } } // Syntax evaluation first, §4.6 int c = spfStatementStrings.Length; LinkedList <Mechanism> mechanisms = new LinkedList <Mechanism>(); Redirect redirect = null; int i; try { for (i = 0; i < c; i++) { SpfQualifier qualifier; spfStatement.Reset(spfStatementStrings[i]); spfStatement.SkipWhitespace(); switch (spfStatement.PeekNextCharacter()) { case '+': spfStatement.Position++; qualifier = SpfQualifier.Pass; break; case '-': spfStatement.Position++; qualifier = SpfQualifier.Fail; break; case '~': spfStatement.Position++; qualifier = SpfQualifier.SoftFail; break; case '?': spfStatement.Position++; qualifier = SpfQualifier.Neutral; break; default: qualifier = SpfQualifier.Pass; break; } switch (spfStatement.NextLabel().ToLower()) { case "all": mechanisms.AddLast(new All(spfStatement, qualifier)); break; case "include": mechanisms.AddLast(new Include(spfStatement, qualifier, spfExpressions)); break; case "a": mechanisms.AddLast(new A(spfStatement, qualifier)); break; case "mx": mechanisms.AddLast(new Mx(spfStatement, qualifier)); break; case "ptr": mechanisms.AddLast(new Ptr(spfStatement, qualifier)); break; case "ip4": mechanisms.AddLast(new Ip4(spfStatement, qualifier)); break; case "ip6": mechanisms.AddLast(new Ip6(spfStatement, qualifier)); break; case "exists": mechanisms.AddLast(new Exists(spfStatement, qualifier)); break; case "redirect": if (!(redirect is null)) { return(new KeyValuePair <SpfResult, string>(SpfResult.PermanentError, "Multiple redirect modifiers found in SPF record.")); } redirect = new Redirect(spfStatement, qualifier); break; case "exp": if (!(explanation is null)) { return(new KeyValuePair <SpfResult, string>(SpfResult.PermanentError, "Multiple exp modifiers found in SPF record.")); } explanation = new Explanation(spfStatement, qualifier); break; default: throw new Exception("Syntax error."); } } foreach (Mechanism mechanism in mechanisms) { await mechanism.Expand(); SpfResult result = await mechanism.Matches(); switch (result) { case SpfResult.Pass: switch (mechanism.Qualifier) { case SpfQualifier.Pass: return(new KeyValuePair <SpfResult, string>(SpfResult.Pass, null)); case SpfQualifier.Fail: return(new KeyValuePair <SpfResult, string>(SpfResult.Fail, explanation == null ? null : await explanation.Evaluate())); case SpfQualifier.Neutral: return(new KeyValuePair <SpfResult, string>(SpfResult.Neutral, null)); case SpfQualifier.SoftFail: return(new KeyValuePair <SpfResult, string>(SpfResult.SoftFail, explanation == null ? null : await explanation.Evaluate())); } break; case SpfResult.TemporaryError: return(new KeyValuePair <SpfResult, string>(SpfResult.TemporaryError, explanation == null ? null : await explanation.Evaluate())); case SpfResult.None: case SpfResult.PermanentError: return(new KeyValuePair <SpfResult, string>(SpfResult.PermanentError, explanation == null ? null : await explanation.Evaluate())); } } if (!(redirect is null)) { await redirect.Expand(); string bak = spfStatement.Domain; spfStatement.Domain = redirect.Domain; try { KeyValuePair <SpfResult, string> result = await SpfResolver.CheckHost(spfStatement, spfExpressions); if (result.Key == SpfResult.None) { return(new KeyValuePair <SpfResult, string>(SpfResult.PermanentError, explanation == null ? null : await explanation.Evaluate())); } else if (result.Key != SpfResult.Pass && result.Key != SpfResult.Neutral && string.IsNullOrEmpty(result.Value)) { return(new KeyValuePair <SpfResult, string>(result.Key, explanation == null ? null : await explanation.Evaluate())); } else { return(result); } } finally { spfStatement.Domain = bak; } } } catch (Exception ex) { return(new KeyValuePair <SpfResult, string>(SpfResult.PermanentError, "Unable to evaluate SPF record: " + FirstRow(ex.Message))); } return(new KeyValuePair <SpfResult, string>(SpfResult.Neutral, null)); }
/// <summary> /// Fetches SPF records, parses them, and /// evaluates them to determine whether a particular host is or is not /// permitted to send mail with a given identity. /// </summary> /// <param name="Term">Information about current query.</param> /// <param name="SpfExpressions">SPF Expressions that can be used, in case a domain lacks SPF records in the DNS.</param> /// <returns>Result of SPF evaluation, together with an optional explanation string, /// if one exists, and if the result indicates a failure.</returns> internal static async Task <KeyValuePair <SpfResult, string> > CheckHost(Term Term, SpfExpression[] SpfExpressions) { Exp Explanation = null; string[] TermStrings = null; string s; try { string[] TXT = await DnsResolver.LookupText(Term.domain); foreach (string Row in TXT) { s = Row.Trim(); if (s.Length > 1 && s[0] == '"' && s[s.Length - 1] == '"') { s = s.Substring(1, s.Length - 2); } if (!s.StartsWith("v=spf1")) { continue; } if (!(TermStrings is null)) { return(new KeyValuePair <SpfResult, string>(SpfResult.PermanentError, "Multiple SPF records found for " + Term.domain + ".")); } TermStrings = s.Substring(6).Trim().Split(space, StringSplitOptions.RemoveEmptyEntries); } } catch (Exception) { TermStrings = null; } if (TermStrings is null) { if (!(SpfExpressions is null)) { foreach (SpfExpression Expression in SpfExpressions) { if (Expression.IsApplicable(Term.domain)) { if (Expression.Spf.StartsWith("v=spf1")) { TermStrings = Expression.Spf.Substring(6).Trim().Split(space, StringSplitOptions.RemoveEmptyEntries); break; } } } } if (TermStrings is null) { return(new KeyValuePair <SpfResult, string>(SpfResult.None, "No SPF records found " + Term.domain + ".")); } } // Syntax evaluation first, §4.6 int c = TermStrings.Length; LinkedList <Mechanism> Mechanisms = new LinkedList <Mechanism>(); Redirect Redirect = null; int i; try { for (i = 0; i < c; i++) { SpfQualifier Qualifier; Term.Reset(TermStrings[i]); Term.SkipWhiteSpace(); switch (Term.PeekNextChar()) { case '+': Term.pos++; Qualifier = SpfQualifier.Pass; break; case '-': Term.pos++; Qualifier = SpfQualifier.Fail; break; case '~': Term.pos++; Qualifier = SpfQualifier.SoftFail; break; case '?': Term.pos++; Qualifier = SpfQualifier.Neutral; break; default: Qualifier = SpfQualifier.Pass; break; } switch (Term.NextLabel().ToLower()) { case "all": Mechanisms.AddLast(new All(Term, Qualifier)); break; case "include": Mechanisms.AddLast(new Include(Term, Qualifier, SpfExpressions)); break; case "a": Mechanisms.AddLast(new A(Term, Qualifier)); break; case "mx": Mechanisms.AddLast(new Mx(Term, Qualifier)); break; case "ptr": Mechanisms.AddLast(new Ptr(Term, Qualifier)); break; case "ip4": Mechanisms.AddLast(new Ip4(Term, Qualifier)); break; case "ip6": Mechanisms.AddLast(new Ip6(Term, Qualifier)); break; case "exists": Mechanisms.AddLast(new Exists(Term, Qualifier)); break; case "redirect": if (!(Redirect is null)) { return(new KeyValuePair <SpfResult, string>(SpfResult.PermanentError, "Multiple redirect modifiers foundin SPF record.")); } Redirect = new Redirect(Term, Qualifier); break; case "exp": if (!(Explanation is null)) { return(new KeyValuePair <SpfResult, string>(SpfResult.PermanentError, "Multiple exp modifiers foundin SPF record.")); } Explanation = new Exp(Term, Qualifier); break; default: throw new Exception("Syntax error."); } } foreach (Mechanism Mechanism in Mechanisms) { await Mechanism.Expand(); SpfResult Result = await Mechanism.Matches(); switch (Result) { case SpfResult.Pass: switch (Mechanism.Qualifier) { case SpfQualifier.Pass: return(new KeyValuePair <SpfResult, string>(SpfResult.Pass, null)); case SpfQualifier.Fail: return(new KeyValuePair <SpfResult, string>(SpfResult.Fail, Explanation == null ? null : await Explanation.Evaluate())); case SpfQualifier.Neutral: return(new KeyValuePair <SpfResult, string>(SpfResult.Neutral, null)); case SpfQualifier.SoftFail: return(new KeyValuePair <SpfResult, string>(SpfResult.SoftFail, Explanation == null ? null : await Explanation.Evaluate())); } break; case SpfResult.TemporaryError: return(new KeyValuePair <SpfResult, string>(SpfResult.TemporaryError, Explanation == null ? null : await Explanation.Evaluate())); case SpfResult.None: case SpfResult.PermanentError: return(new KeyValuePair <SpfResult, string>(SpfResult.PermanentError, Explanation == null ? null : await Explanation.Evaluate())); } } if (!(Redirect is null)) { await Redirect.Expand(); string Bak = Term.domain; Term.domain = Redirect.Domain; try { KeyValuePair <SpfResult, string> Result = await SpfResolver.CheckHost(Term, SpfExpressions); if (Result.Key == SpfResult.None) { return(new KeyValuePair <SpfResult, string>(SpfResult.PermanentError, Explanation == null ? null : await Explanation.Evaluate())); } else if (Result.Key != SpfResult.Pass && Result.Key != SpfResult.Neutral && string.IsNullOrEmpty(Result.Value)) { return(new KeyValuePair <SpfResult, string>(Result.Key, Explanation == null ? null : await Explanation.Evaluate())); } else { return(Result); } } finally { Term.domain = Bak; } } } catch (Exception ex) { return(new KeyValuePair <SpfResult, string>(SpfResult.PermanentError, "Unable to evaluate SPF record: " + FirstRow(ex.Message))); } return(new KeyValuePair <SpfResult, string>(SpfResult.Neutral, null)); }