private async Task <bool?> IsIpMatchAsync <TRecord>(DomainName domain, IPAddress ipAddress, int?prefix, RecordType recordType, CancellationToken token)
            where TRecord : DnsRecordBase, IAddressRecord
        {
            DnsResolveResult <TRecord> dnsResult = await ResolveDnsAsync <TRecord>(domain, recordType, token);

            if ((dnsResult == null) || ((dnsResult.ReturnCode != ReturnCode.NoError) && (dnsResult.ReturnCode != ReturnCode.NxDomain)))
            {
                return(null);
            }

            foreach (var dnsRecord in dnsResult.Records)
            {
                if (prefix.HasValue)
                {
                    if (ipAddress.Equals(dnsRecord.Address.GetNetworkAddress(prefix.Value)))
                    {
                        return(true);
                    }
                }
                else
                {
                    if (ipAddress.Equals(dnsRecord.Address))
                    {
                        return(true);
                    }
                }
            }

            return(false);
        }
Пример #2
0
		protected override async Task<LoadRecordResult> LoadRecordsAsync(DomainName domain, CancellationToken token)
		{
			DnsResolveResult<TxtRecord> dnsResult = await ResolveDnsAsync<TxtRecord>(domain, RecordType.Txt, token);
			if ((dnsResult == null) || ((dnsResult.ReturnCode != ReturnCode.NoError) && (dnsResult.ReturnCode != ReturnCode.NxDomain)))
			{
				return new LoadRecordResult() { CouldBeLoaded = false, ErrorResult = SpfQualifier.TempError };
			}
			else if ((Scope == SenderIDScope.Pra) && (dnsResult.ReturnCode == ReturnCode.NxDomain))
			{
				return new LoadRecordResult() { CouldBeLoaded = false, ErrorResult = SpfQualifier.Fail };
			}

			var senderIDTextRecords = dnsResult.Records
				.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
					{
						return new LoadRecordResult() { CouldBeLoaded = false, ErrorResult = SpfQualifier.PermError };
					}
				}

				if (potentialRecords.GroupBy(r => r.Version).Any(g => g.Count() > 1))
				{
					return new LoadRecordResult() { CouldBeLoaded = false, ErrorResult = SpfQualifier.PermError };
				}
				else
				{
					return new LoadRecordResult() { CouldBeLoaded = true, ErrorResult = default(SpfQualifier), Record = potentialRecords.OrderByDescending(r => r.Version).First() };
				}
			}
			else
			{
				return new LoadRecordResult() { CouldBeLoaded = false, ErrorResult = SpfQualifier.None };
			}
		}
Пример #3
0
        protected override async Task <LoadRecordResult> LoadRecordsAsync(DomainName domain, CancellationToken token)
        {
            DnsResolveResult <TxtRecord> dnsResult = await ResolveDnsAsync <TxtRecord>(domain, RecordType.Txt, token).ConfigureAwait(false);

            if ((dnsResult == null) || ((dnsResult.ReturnCode != ReturnCode.NoError) && (dnsResult.ReturnCode != ReturnCode.NxDomain)))
            {
                return(new LoadRecordResult()
                {
                    CouldBeLoaded = false, ErrorResult = SpfQualifier.TempError
                });
            }

            var spfTextRecords = dnsResult.Records
                                 .Select(r => r.TextData)
                                 .Where(SpfRecord.IsSpfRecord)
                                 .ToList();

            SpfRecord record;

            if (spfTextRecords.Count == 0)
            {
                return(new LoadRecordResult()
                {
                    CouldBeLoaded = false, ErrorResult = SpfQualifier.None
                });
            }
            else if ((spfTextRecords.Count > 1) || !SpfRecord.TryParse(spfTextRecords[0], out record))
            {
                return(new LoadRecordResult()
                {
                    CouldBeLoaded = false, ErrorResult = SpfQualifier.PermError
                });
            }
            else
            {
                return(new LoadRecordResult()
                {
                    CouldBeLoaded = true, Record = record
                });
            }
        }
        private async Task <string> ExpandMacroAsync(Match pattern, IPAddress ip, DomainName domain, string sender, CancellationToken token)
        {
            switch (pattern.Value)
            {
            case "%%":
                return("%");

            case "%_":
                return("_");

            case "%-":
                return("-");

            default:
                string letter;
                switch (pattern.Groups["letter"].Value)
                {
                case "s":
                    letter = sender;
                    break;

                case "l":
                    // no boundary check needed, sender is validated on start of CheckHost
                    letter = sender.Split('@')[0];
                    break;

                case "o":
                    // no boundary check needed, sender is validated on start of CheckHost
                    letter = sender.Split('@')[1];
                    break;

                case "d":
                    letter = domain.ToString();
                    break;

                case "i":
                    letter = String.Join(".", ip.GetAddressBytes().Select(b => b.ToString()));
                    break;

                case "p":
                    letter = "unknown";

                    DnsResolveResult <PtrRecord> dnsResult = await ResolveDnsAsync <PtrRecord>(ip.GetReverseLookupDomain(), RecordType.Ptr, token);

                    if ((dnsResult == null) || ((dnsResult.ReturnCode != ReturnCode.NoError) && (dnsResult.ReturnCode != ReturnCode.NxDomain)))
                    {
                        break;
                    }

                    int ptrCheckedCount = 0;
                    foreach (PtrRecord ptrRecord in dnsResult.Records)
                    {
                        if (++ptrCheckedCount == 10)
                        {
                            break;
                        }

                        bool?isPtrMatch = await IsIpMatchAsync(ptrRecord.PointerDomainName, ip, 0, 0, token);

                        if (isPtrMatch.HasValue && isPtrMatch.Value)
                        {
                            if (letter == "unknown" || ptrRecord.PointerDomainName.IsSubDomainOf(domain))
                            {
                                // use value, if first record or subdomain
                                // but evaluate the other records
                                letter = ptrRecord.PointerDomainName.ToString();
                            }
                            else if (ptrRecord.PointerDomainName.Equals(domain))
                            {
                                // ptr equal domain --> best match, use it
                                letter = ptrRecord.PointerDomainName.ToString();
                                break;
                            }
                        }
                    }
                    break;

                case "v":
                    letter = (ip.AddressFamily == AddressFamily.InterNetworkV6) ? "ip6" : "in-addr";
                    break;

                case "h":
                    letter = HeloDomain?.ToString() ?? "unknown";
                    break;

                case "c":
                    IPAddress address =
                        LocalIP
                        ?? NetworkInterface.GetAllNetworkInterfaces()
                        .Where(n => (n.OperationalStatus == OperationalStatus.Up) && (n.NetworkInterfaceType != NetworkInterfaceType.Loopback))
                        .SelectMany(n => n.GetIPProperties().UnicastAddresses)
                        .Select(u => u.Address)
                        .FirstOrDefault(a => a.AddressFamily == ip.AddressFamily)
                        ?? ((ip.AddressFamily == AddressFamily.InterNetwork) ? IPAddress.Loopback : IPAddress.IPv6Loopback);
                    letter = address.ToString();
                    break;

                case "r":
                    letter = LocalDomain?.ToString() ?? System.Net.Dns.GetHostName();
                    break;

                case "t":
                    letter = ((int)(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc) - DateTime.Now).TotalSeconds).ToString();
                    break;

                default:
                    return(null);
                }

                // only letter
                if (pattern.Value.Length == 4)
                {
                    return(letter);
                }

                char[] delimiters = pattern.Groups["delimiter"].Value.ToCharArray();
                if (delimiters.Length == 0)
                {
                    delimiters = new[] { '.' }
                }
                ;

                string[] parts = letter.Split(delimiters);

                if (pattern.Groups["reverse"].Value == "r")
                {
                    parts = parts.Reverse().ToArray();
                }

                int count = Int32.MaxValue;
                if (!String.IsNullOrEmpty(pattern.Groups["count"].Value))
                {
                    count = Int32.Parse(pattern.Groups["count"].Value);
                }

                if (count < 1)
                {
                    return(null);
                }

                count = Math.Min(count, parts.Length);

                return(String.Join(".", parts, (parts.Length - count), count));
            }
        }
    }
        private async Task <SpfQualifier> CheckMechanismAsync(SpfMechanism mechanism, IPAddress ip, DomainName domain, string sender, State state, CancellationToken token)
        {
            switch (mechanism.Type)
            {
            case SpfMechanismType.All:
                return(mechanism.Qualifier);

            case SpfMechanismType.A:
                if (++state.DnsLookupCount > 10)
                {
                    return(SpfQualifier.PermError);
                }

                DomainName aMechanismDomain = String.IsNullOrEmpty(mechanism.Domain) ? domain : await ExpandDomainAsync(mechanism.Domain, ip, domain, sender, token);

                bool?isAMatch = await IsIpMatchAsync(aMechanismDomain, ip, mechanism.Prefix, mechanism.Prefix6, token);

                if (!isAMatch.HasValue)
                {
                    return(SpfQualifier.TempError);
                }

                if (isAMatch.Value)
                {
                    return(mechanism.Qualifier);
                }
                break;

            case SpfMechanismType.Mx:
                if (++state.DnsLookupCount > 10)
                {
                    return(SpfQualifier.PermError);
                }

                DomainName mxMechanismDomain = String.IsNullOrEmpty(mechanism.Domain) ? domain : await ExpandDomainAsync(mechanism.Domain, ip, domain, sender, token);

                DnsResolveResult <MxRecord> dnsMxResult = await ResolveDnsAsync <MxRecord>(mxMechanismDomain, RecordType.Mx, token);

                if ((dnsMxResult == null) || ((dnsMxResult.ReturnCode != ReturnCode.NoError) && (dnsMxResult.ReturnCode != ReturnCode.NxDomain)))
                {
                    return(SpfQualifier.TempError);
                }

                int mxCheckedCount = 0;

                foreach (MxRecord mxRecord in dnsMxResult.Records)
                {
                    if (++mxCheckedCount == 10)
                    {
                        break;
                    }

                    bool?isMxMatch = await IsIpMatchAsync(mxRecord.ExchangeDomainName, ip, mechanism.Prefix, mechanism.Prefix6, token);

                    if (!isMxMatch.HasValue)
                    {
                        return(SpfQualifier.TempError);
                    }

                    if (isMxMatch.Value)
                    {
                        return(mechanism.Qualifier);
                    }
                }
                break;

            case SpfMechanismType.Ip4:
            case SpfMechanismType.Ip6:
                IPAddress compareAddress;
                if (IPAddress.TryParse(mechanism.Domain, out compareAddress))
                {
                    if (ip.AddressFamily != compareAddress.AddressFamily)
                    {
                        return(SpfQualifier.None);
                    }

                    if (mechanism.Prefix.HasValue)
                    {
                        if ((mechanism.Prefix.Value < 0) || (mechanism.Prefix.Value > (compareAddress.AddressFamily == AddressFamily.InterNetworkV6 ? 128 : 32)))
                        {
                            return(SpfQualifier.PermError);
                        }

                        if (ip.GetNetworkAddress(mechanism.Prefix.Value).Equals(compareAddress.GetNetworkAddress(mechanism.Prefix.Value)))
                        {
                            return(mechanism.Qualifier);
                        }
                    }
                    else if (ip.Equals(compareAddress))
                    {
                        return(mechanism.Qualifier);
                    }
                }
                else
                {
                    return(SpfQualifier.PermError);
                }

                break;

            case SpfMechanismType.Ptr:
                if (++state.DnsLookupCount > 10)
                {
                    return(SpfQualifier.PermError);
                }

                DnsResolveResult <PtrRecord> dnsPtrResult = await ResolveDnsAsync <PtrRecord>(ip.GetReverseLookupDomain(), RecordType.Ptr, token);

                if ((dnsPtrResult == null) || ((dnsPtrResult.ReturnCode != ReturnCode.NoError) && (dnsPtrResult.ReturnCode != ReturnCode.NxDomain)))
                {
                    return(SpfQualifier.TempError);
                }

                DomainName ptrMechanismDomain = String.IsNullOrEmpty(mechanism.Domain) ? domain : await ExpandDomainAsync(mechanism.Domain, ip, domain, sender, token);

                int ptrCheckedCount = 0;
                foreach (PtrRecord ptrRecord in dnsPtrResult.Records)
                {
                    if (++ptrCheckedCount == 10)
                    {
                        break;
                    }

                    bool?isPtrMatch = await IsIpMatchAsync(ptrRecord.PointerDomainName, ip, 0, 0, token);

                    if (isPtrMatch.HasValue && isPtrMatch.Value)
                    {
                        if (ptrRecord.PointerDomainName.Equals(ptrMechanismDomain) || (ptrRecord.PointerDomainName.IsSubDomainOf(ptrMechanismDomain)))
                        {
                            return(mechanism.Qualifier);
                        }
                    }
                }
                break;

            case SpfMechanismType.Exist:
                if (++state.DnsLookupCount > 10)
                {
                    return(SpfQualifier.PermError);
                }

                if (String.IsNullOrEmpty(mechanism.Domain))
                {
                    return(SpfQualifier.PermError);
                }

                DomainName existsMechanismDomain = String.IsNullOrEmpty(mechanism.Domain) ? domain : await ExpandDomainAsync(mechanism.Domain, ip, domain, sender, token);

                DnsResolveResult <ARecord> dnsAResult = await ResolveDnsAsync <ARecord>(existsMechanismDomain, RecordType.A, token);

                if ((dnsAResult == null) || ((dnsAResult.ReturnCode != ReturnCode.NoError) && (dnsAResult.ReturnCode != ReturnCode.NxDomain)))
                {
                    return(SpfQualifier.TempError);
                }

                if (dnsAResult.Records.Count(record => (record.RecordType == RecordType.A)) > 0)
                {
                    return(mechanism.Qualifier);
                }
                break;

            case SpfMechanismType.Include:
                if (++state.DnsLookupCount > 10)
                {
                    return(SpfQualifier.PermError);
                }

                if (String.IsNullOrEmpty(mechanism.Domain))
                {
                    return(SpfQualifier.PermError);
                }

                DomainName includeMechanismDomain = String.IsNullOrEmpty(mechanism.Domain) ? domain : await ExpandDomainAsync(mechanism.Domain, ip, domain, sender, token);

                if (includeMechanismDomain.Equals(domain))
                {
                    return(SpfQualifier.PermError);
                }

                var includeResult = await CheckHostInternalAsync(ip, includeMechanismDomain, sender, false, state, token);

                switch (includeResult.Result)
                {
                case SpfQualifier.Pass:
                    return(mechanism.Qualifier);

                case SpfQualifier.Fail:
                case SpfQualifier.SoftFail:
                case SpfQualifier.Neutral:
                    return(SpfQualifier.None);

                case SpfQualifier.TempError:
                    return(SpfQualifier.TempError);

                case SpfQualifier.PermError:
                case SpfQualifier.None:
                    return(SpfQualifier.PermError);
                }
                break;

            default:
                return(SpfQualifier.PermError);
            }

            return(SpfQualifier.None);
        }
        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);
        }