internal SpfQualifier CheckHost(SpfCheckHostParameter parameters) { if (String.IsNullOrEmpty(parameters.Sender)) { return(SpfQualifier.PermError); } if (!parameters.Sender.Contains('@')) { parameters.Sender = "postmaster@" + parameters.Sender; } if (parameters.LoopCount > 15) { return(SpfQualifier.PermError); } if ((Terms == null) || (Terms.Count == 0)) { return(SpfQualifier.Neutral); } foreach (SpfTerm term in Terms) { SpfQualifier qualifier = term.CheckHost(parameters); if (qualifier != SpfQualifier.None) { return(qualifier); } } return(SpfQualifier.Neutral); }
public Ip4(SpfStatement spfStatement, SpfQualifier qualifier) : base(spfStatement, qualifier) { if (IpAddress.AddressFamily != AddressFamily.InterNetwork) { throw new Exception("IPv4 address expected."); } }
/// <summary> /// This mechanisms tests whether <ip> is contained within a given IP6 network. /// </summary> /// <param name="Term">Term</param> /// <param name="Qualifier">Qualifier</param> public Ip6(Term Term, SpfQualifier Qualifier) : base(Term, Qualifier) { if (this.address.AddressFamily != AddressFamily.InterNetworkV6) { throw new Exception("Expected IP6 address."); } }
public Ip(SpfStatement spfStatement, SpfQualifier qualifier) : base(spfStatement, qualifier) { if (SpfStatement.PeekNextCharacter() != ':') { throw new Exception(": expected."); } SpfStatement.NextCharacter(); int start = SpfStatement.Position; char ch; while (SpfStatement.Position < SpfStatement.Statement.Length && (ch = SpfStatement.Statement[SpfStatement.Position]) != '/' && ch > ' ') { SpfStatement.Position++; } if (!IPAddress.TryParse(SpfStatement.Statement.Substring(start, SpfStatement.Position - start), out IpAddress)) { throw new Exception("IP Address expected."); } int max; switch (IpAddress.AddressFamily) { case AddressFamily.InterNetwork: max = 32; break; case AddressFamily.InterNetworkV6: max = 128; break; default: throw new Exception("IP Address expected."); } if (SpfStatement.PeekNextCharacter() == '/') { SpfStatement.NextCharacter(); Cidr = SpfStatement.NextInteger(); if (Cidr < 0 || Cidr > max) { throw new Exception("Invalid CIDR"); } } else { Cidr = max; } }
protected override bool TryLoadRecords(string domain, out SpfRecord record, out SpfQualifier errorResult) { if (!TryLoadRecords(domain, RecordType.Spf, out record, out errorResult)) { return((errorResult == SpfQualifier.None) && TryLoadRecords(domain, RecordType.Txt, out record, out errorResult)); } else { return(true); } }
/// <summary> /// Abstract base class for IP-based SPF mechanisms. /// </summary> /// <param name="Term">Term</param> /// <param name="Qualifier">Qualifier</param> public Ip(Term Term, SpfQualifier Qualifier) : base(Term, Qualifier) { if (Term.PeekNextChar() != ':') { throw new Exception(": expected."); } Term.NextChar(); int Start = Term.pos; char ch; while (Term.pos < Term.len && (ch = Term.s[Term.pos]) != '/' && ch > ' ') { Term.pos++; } if (!IPAddress.TryParse(Term.s.Substring(Start, Term.pos - Start), out this.address)) { throw new Exception("IP Address expected."); } int Max; switch (this.address.AddressFamily) { case AddressFamily.InterNetwork: Max = 32; break; case AddressFamily.InterNetworkV6: Max = 128; break; default: throw new Exception("IP Address expected."); } if (Term.PeekNextChar() == '/') { Term.NextChar(); this.cidr = Term.NextInteger(); if (this.cidr < 0 || this.cidr > Max) { throw new Exception("Invalid CIDR"); } } else { this.cidr = Max; } }
/// <summary> /// Abstract base class for SPF mechanisms with a domain specification and /// an optional CIDR specification. /// </summary> /// <param name="Term">Term</param> /// <param name="Qualifier">Qualifier</param> public MechanismDomainCidrSpec(Term Term, SpfQualifier Qualifier) : base(Term, Qualifier) { if (Term.PeekNextChar() == '/') { Term.NextChar(); bool HasIp4; if (HasIp4 = char.IsDigit(Term.PeekNextChar())) { this.ip4Cidr = Term.NextInteger(); if (this.ip4Cidr < 0 || this.ip4Cidr > 32) { throw new Exception("Invalid IP4 CIDR"); } } if (Term.PeekNextChar() == '/') { Term.NextChar(); if (HasIp4 && Term.PeekNextChar() == '/') { Term.NextChar(); } if (char.IsDigit(Term.PeekNextChar())) { this.ip6Cidr = Term.NextInteger(); if (this.ip6Cidr < 0 || this.ip4Cidr > 128) { throw new Exception("Invalid IP6 CIDR"); } } else if (!HasIp4) { throw new Exception("IP4 or IP6 CIDR expected."); } } } }
public DomainCidrSpecification(SpfStatement statement, SpfQualifier spfQualifier) : base(statement, spfQualifier) { if (statement.PeekNextCharacter() == '/') { statement.NextCharacter(); bool hasIpv4; if (hasIpv4 = char.IsDigit(statement.PeekNextCharacter())) { Ipv4Cidr = statement.NextInteger(); if (Ipv4Cidr < 0 || Ipv4Cidr > 32) { throw new Exception("Invalid IPv4 CIDR"); } } if (statement.PeekNextCharacter() == '/') { statement.NextCharacter(); if (hasIpv4 && statement.PeekNextCharacter() == '/') { statement.NextCharacter(); } if (char.IsDigit(statement.PeekNextCharacter())) { Ipv6Cidr = statement.NextInteger(); if (Ipv6Cidr < 0 || Ipv6Cidr > 128) { throw new Exception("Invalid IPv6 CIDR"); } } else if (!hasIpv4) { throw new Exception("IPv4 or IPv6 CIDR expected."); } } } }
public DomainSpecification(SpfStatement spfStatement, SpfQualifier qualifier) : base(spfStatement, qualifier) { if (SpfStatement.PeekNextCharacter() == Separator) { SpfStatement.NextCharacter(); var start = SpfStatement.Position; char ch; while ((ch = SpfStatement.PeekNextCharacter()) > ' ' && ch != '/') { SpfStatement.Position++; } Domain = SpfStatement.Statement.Substring(start, SpfStatement.Position - start); } else if (DomainRequired) { throw new Exception($"{Separator} expected"); } }
/// <summary> /// Abstract base class for SPF mechanisms with a domain specification. /// </summary> /// <param name="Term">Term</param> /// <param name="Qualifier">Qualifier</param> public MechanismDomainSpec(Term Term, SpfQualifier Qualifier) : base(Term, Qualifier) { if (Term.PeekNextChar() == this.Separator) { Term.NextChar(); int Start = Term.pos; char ch; while ((ch = Term.PeekNextChar()) > ' ' && ch != '/') { Term.pos++; } this.domain = Term.s.Substring(Start, Term.pos - Start); } else if (this.DomainRequired) { throw new Exception(this.Separator + " expected."); } }
/// <summary> /// Abstract base class for SPF Mechanisms. /// </summary> /// <param name="Term">Term</param> /// <param name="Qualifier">Qualifier</param> public Mechanism(Term Term, SpfQualifier Qualifier) { this.term = Term; this.qualifier = Qualifier; }
/// <summary> /// This mechanism is used to construct an arbitrary domain name that is /// used for a DNS A record query.It allows for complicated schemes /// involving arbitrary parts of the mail envelope to determine what is /// permitted. /// </summary> /// <param name="Term">Term</param> /// <param name="Qualifier">Qualifier</param> public Exists(Term Term, SpfQualifier Qualifier) : base(Term, Qualifier) { }
public Redirect(SpfStatement spfStatement, SpfQualifier qualifier) : base(spfStatement, qualifier) { }
public Explanation(SpfStatement spfStatement, SpfQualifier qualifier) : base(spfStatement, qualifier) { }
public Ptr(SpfStatement spfStatement, SpfQualifier qualifier) : base(spfStatement, qualifier) { }
public Include(SpfStatement spfStatement, SpfQualifier qualifier, params SpfExpression[] spfExpressions) : base(spfStatement, qualifier) { _spfExpressions = spfExpressions; }
public Exists(SpfStatement spfStatement, SpfQualifier qualifier) : base(spfStatement, qualifier) { }
public A(SpfStatement statement, SpfQualifier spfQualifier) : base(statement, spfQualifier) { }
public Mechanism(SpfStatement spfStatement, SpfQualifier qualifier) { Qualifier = qualifier; SpfStatement = spfStatement; }
/// <summary> /// This mechanism tests whether the DNS reverse-mapping for <ip> exists /// and correctly points to a domain name within a particular domain. /// This mechanism SHOULD NOT be published.See the note at the end of /// this section for more information. /// </summary> /// <param name="Term">Term</param> /// <param name="Qualifier">Qualifier</param> public Ptr(Term Term, SpfQualifier Qualifier) : base(Term, Qualifier) { }
/// <summary> /// This mechanism matches if <ip> is one of the MX hosts for a domain name. /// </summary> /// <param name="Term">Term</param> /// <param name="Qualifier">Qualifier</param> internal Mx(Term Term, SpfQualifier Qualifier) : base(Term, Qualifier) { }
protected abstract bool TryLoadRecords(string domain, out T record, out SpfQualifier errorResult);
private bool TryLoadRecords(string domain, RecordType recordType, out SpfRecord record, out SpfQualifier errorResult) { DnsMessage dnsMessage = ResolveDns(domain, recordType); if ((dnsMessage == null) || ((dnsMessage.ReturnCode != ReturnCode.NoError) && (dnsMessage.ReturnCode != ReturnCode.NxDomain))) { record = default(SpfRecord); errorResult = SpfQualifier.TempError; return(false); } var spfTextRecords = dnsMessage.AnswerRecords .Where(r => r.RecordType == recordType) .Cast <ITextRecord>() .Select(r => r.TextData) .Where(SpfRecord.IsSpfRecord).ToList(); if (spfTextRecords.Count == 0) { record = default(SpfRecord); errorResult = SpfQualifier.None; return(false); } else if ((spfTextRecords.Count > 1) || !SpfRecord.TryParse(spfTextRecords[0], out record)) { record = default(SpfRecord); errorResult = SpfQualifier.PermError; return(false); } else { errorResult = default(SpfQualifier); return(true); } }
private async Task <ValidationResult> CheckHostInternalAsync(IPAddress ip, DomainName domain, string sender, bool expandExplanation, State state, CancellationToken token) { if ((domain == null) || (domain.Equals(DomainName.Root))) { return(new ValidationResult() { Result = SpfQualifier.None, Explanation = String.Empty }); } if (String.IsNullOrEmpty(sender)) { sender = "postmaster@unknown"; } else if (!sender.Contains('@')) { sender = "postmaster@" + sender; } LoadRecordResult loadResult = await LoadRecordsAsync(domain, token); if (!loadResult.CouldBeLoaded) { return(new ValidationResult() { Result = loadResult.ErrorResult, Explanation = String.Empty }); } T record = loadResult.Record; if ((record.Terms == null) || (record.Terms.Count == 0)) { return new ValidationResult() { Result = SpfQualifier.Neutral, Explanation = String.Empty } } ; if (record.Terms.OfType <SpfModifier>().GroupBy(m => m.Type).Where(g => (g.Key == SpfModifierType.Exp) || (g.Key == SpfModifierType.Redirect)).Any(g => g.Count() > 1)) { return new ValidationResult() { Result = SpfQualifier.PermError, Explanation = String.Empty } } ; ValidationResult result = new ValidationResult() { Result = loadResult.ErrorResult }; #region Evaluate mechanism foreach (SpfMechanism mechanism in record.Terms.OfType <SpfMechanism>()) { if (state.DnsLookupCount > DnsLookupLimit) { return new ValidationResult() { Result = SpfQualifier.PermError, Explanation = String.Empty } } ; SpfQualifier qualifier = await CheckMechanismAsync(mechanism, ip, domain, sender, state, token); if (qualifier != SpfQualifier.None) { result.Result = qualifier; break; } } #endregion #region Evaluate modifiers if (result.Result == SpfQualifier.None) { SpfModifier redirectModifier = record.Terms.OfType <SpfModifier>().FirstOrDefault(m => m.Type == SpfModifierType.Redirect); if (redirectModifier != null) { if (++state.DnsLookupCount > 10) { return new ValidationResult() { Result = SpfQualifier.PermError, Explanation = String.Empty } } ; DomainName redirectDomain = await ExpandDomainAsync(redirectModifier.Domain ?? String.Empty, ip, domain, sender, token); if ((redirectDomain == null) || (redirectDomain == DomainName.Root) || (redirectDomain.Equals(domain))) { result.Result = SpfQualifier.PermError; } else { result = await CheckHostInternalAsync(ip, redirectDomain, sender, expandExplanation, state, token); if (result.Result == SpfQualifier.None) { result.Result = SpfQualifier.PermError; } } } } else if ((result.Result == SpfQualifier.Fail) && expandExplanation) { SpfModifier expModifier = record.Terms.OfType <SpfModifier>().FirstOrDefault(m => m.Type == SpfModifierType.Exp); if (expModifier != null) { DomainName target = await ExpandDomainAsync(expModifier.Domain, ip, domain, sender, token); if ((target == null) || (target.Equals(DomainName.Root))) { result.Explanation = String.Empty; } else { DnsResolveResult <TxtRecord> dnsResult = await ResolveDnsAsync <TxtRecord>(target, RecordType.Txt, token); if ((dnsResult != null) && (dnsResult.ReturnCode == ReturnCode.NoError)) { TxtRecord txtRecord = dnsResult.Records.FirstOrDefault(); if (txtRecord != null) { result.Explanation = (await ExpandMacroAsync(txtRecord.TextData, ip, domain, sender, token)).ToString(); } } } } } #endregion if (result.Result == SpfQualifier.None) { result.Result = SpfQualifier.Neutral; } return(result); }
/// <summary> /// The "all" mechanism is a test that always matches. It is used as the /// rightmost mechanism in a record to provide an explicit default. /// </summary> /// <param name="Term">Term</param> /// <param name="Qualifier">Qualifier</param> public All(Term Term, SpfQualifier Qualifier) : base(Term, Qualifier) { }
private SpfQualifier CheckHostInternal(IPAddress ip, string sender, string domain, bool expandExplanation, out string explanation) { explanation = String.Empty; if (String.IsNullOrEmpty(domain)) { return(SpfQualifier.None); } if (String.IsNullOrEmpty(sender)) { sender = "postmaster@unknown"; } else if (!sender.Contains('@')) { sender = "postmaster@" + sender; } SpfQualifier result; T record; if (!TryLoadRecords(domain, out record, out result)) { return(result); } if ((record.Terms == null) || (record.Terms.Count == 0)) { return(SpfQualifier.Neutral); } if (record.Terms.OfType <SpfModifier>().GroupBy(m => m.Type).Where(g => (g.Key == SpfModifierType.Exp) || (g.Key == SpfModifierType.Redirect)).Any(g => g.Count() > 1)) { return(SpfQualifier.PermError); } #region Evaluate mechanism foreach (SpfMechanism mechanism in record.Terms.OfType <SpfMechanism>()) { if (LookupCount > DnsLookupLimit) { return(SpfQualifier.PermError); } SpfQualifier qualifier = CheckMechanism(mechanism, ip, sender, domain); if (qualifier != SpfQualifier.None) { result = qualifier; break; } } #endregion #region Evaluate modifiers if (result == SpfQualifier.None) { SpfModifier redirectModifier = record.Terms.OfType <SpfModifier>().FirstOrDefault(m => m.Type == SpfModifierType.Redirect); if (redirectModifier != null) { string redirectDomain = ExpandDomain(redirectModifier.Domain ?? String.Empty, ip, sender, domain); if (String.IsNullOrEmpty(redirectDomain) || (redirectDomain.Equals(domain, StringComparison.InvariantCultureIgnoreCase))) { result = SpfQualifier.PermError; } else { result = CheckHostInternal(ip, sender, redirectDomain, expandExplanation, out explanation); if (result == SpfQualifier.None) { result = SpfQualifier.PermError; } } } } else if ((result == SpfQualifier.Fail) && expandExplanation) { SpfModifier expModifier = record.Terms.OfType <SpfModifier>().Where(m => m.Type == SpfModifierType.Exp).FirstOrDefault(); if (expModifier != null) { string target = ExpandDomain(expModifier.Domain, ip, sender, domain); if (String.IsNullOrEmpty(target)) { explanation = String.Empty; } else { DnsMessage dnsMessage = ResolveDns(target, RecordType.Txt); if ((dnsMessage != null) && (dnsMessage.ReturnCode == ReturnCode.NoError)) { TxtRecord txtRecord = dnsMessage.AnswerRecords.OfType <TxtRecord>().FirstOrDefault(); if (txtRecord != null) { explanation = ExpandDomain(txtRecord.TextData, ip, sender, domain); } } } } } #endregion return((result != SpfQualifier.None) ? result : SpfQualifier.Neutral); }
private bool TryLoadRecords(string domain, RecordType recordType, out SenderIDRecord record, out SpfQualifier errorResult) { DnsMessage dnsMessage = ResolveDns(domain, recordType); if ((dnsMessage == null) || ((dnsMessage.ReturnCode != ReturnCode.NoError) && (dnsMessage.ReturnCode != ReturnCode.NxDomain))) { record = default(SenderIDRecord); errorResult = SpfQualifier.TempError; return(false); } else if ((Scope == SenderIDScope.Pra) && (dnsMessage.ReturnCode == ReturnCode.NxDomain)) { record = default(SenderIDRecord); errorResult = SpfQualifier.Fail; return(false); } var senderIDTextRecords = dnsMessage.AnswerRecords .Where(r => r.RecordType == recordType) .Cast <ITextRecord>() .Select(r => r.TextData) .Where(t => SenderIDRecord.IsSenderIDRecord(t, Scope)).ToList(); if (senderIDTextRecords.Count >= 1) { var potentialRecords = new List <SenderIDRecord>(); foreach (var senderIDTextRecord in senderIDTextRecords) { SenderIDRecord tmpRecord; if (SenderIDRecord.TryParse(senderIDTextRecord, out tmpRecord)) { potentialRecords.Add(tmpRecord); } else { record = default(SenderIDRecord); errorResult = SpfQualifier.PermError; return(false); } } if (potentialRecords.GroupBy(r => r.Version).Any(g => g.Count() > 1)) { record = default(SenderIDRecord); errorResult = SpfQualifier.PermError; return(false); } else { record = potentialRecords.OrderByDescending(r => r.Version).First(); errorResult = default(SpfQualifier); return(true); } } else { record = default(SenderIDRecord); errorResult = SpfQualifier.None; return(false); } }
/// <summary> /// The "redirect" modifier is intended for consolidating both /// authorizations and policy into a common set to be shared within a /// single ADMD.It is possible to control both authorized hosts and /// policy for an arbitrary number of domains from a single record. /// </summary> /// <param name="Term">Term</param> /// <param name="Qualifier">Qualifier</param> public Redirect(Term Term, SpfQualifier Qualifier) : base(Term, Qualifier) { }
/// <summary> /// The "include" mechanism triggers a recursive evaluation of check_host(). /// </summary> /// <param name="Term">Term</param> /// <param name="Qualifier">Qualifier</param> /// <param name="SpfExpressions">SPF Expressions that can be used, in case a domain lacks SPF records in the DNS.</param> public Include(Term Term, SpfQualifier Qualifier, params SpfExpression[] SpfExpressions) : base(Term, Qualifier) { this.spfExpressions = SpfExpressions; }