Exemple #1
0
        /// <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));
        }
Exemple #2
0
        public async Task should_get_correct_spf_result(string ipAddress, string domain, string sender,
                                                        string heloDomain, string hostDomain, SpfResult expectedResult)
        {
            KeyValuePair <SpfResult, string> result = await SpfResolver.CheckHost(IPAddress.Parse(ipAddress),
                                                                                  domain, sender, heloDomain, hostDomain);

            Assert.AreEqual(expectedResult, result.Key, result.Value);
        }
        /// <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));
        }