spf.go
func (spf *SPF) CheckIP(ip_str string) (string, error) {
	ip := net.ParseIP(ip_str)
	for _, ip_net := range spf.Fail {
		if ip_net.Contains(ip) {
			return "Fail", nil
		}
	}
	for _, ip_net := range spf.SoftFail {
		if ip_net.Contains(ip) {
			return "SoftFail", nil
		}
	}
	for _, ip_net := range spf.Neutral {
		if ip_net.Contains(ip) {
			return "Neutral", nil
		}
	}
	for _, ip_net := range spf.Pass {
		if ip_net.Contains(ip) {
			return "Pass", nil
		}
	}

	for _, include := range spf.Includes {
		/*
			RFC 7208 5.2
				The "include" mechanism triggers a recursive evaluation of
				check_host().

				1.  The <domain-spec> is expanded as per Section 7.

				2.  check_host() is evaluated with the resulting string as the
					<domain>.  The <ip> and <sender> arguments remain the same as in
					the current evaluation of check_host().

				3.  The recursive evaluation returns match, not-match, or an error.

				4.  If it returns match, then the appropriate result for the
					"include" mechanism is used (e.g., include or +include produces a
					"pass" result and -include produces "fail").

				5.  If it returns not-match or an error, the parent check_host()
					resumes processing as per the table below, with the previous
					value of <domain> restored.

				+---------------------------------+---------------------------------+
				| A recursive check_host() result | Causes the "include" mechanism  |
				| of:                             | to:                             |
				+---------------------------------+---------------------------------+
				| pass                            | match                           |
				|                                 |                                 |
				| fail                            | not match                       |
				|                                 |                                 |
				| softfail                        | not match                       |
				|                                 |                                 |
				| neutral                         | not match                       |
				|                                 |                                 |
				| temperror                       | return temperror                |
				|                                 |                                 |
				| permerror                       | return permerror                |
				|                                 |                                 |
				| none                            | return permerror                |
				+---------------------------------+---------------------------------+
		*/
		check, err := include.spf.CheckIP(ip_str)
		if err != nil {
			return "", nil
		}
		if check == "Pass" {
			return qualifierToResult(include.qualifier), nil
		}
	}

	// Check redirects
	/*
		RFC 7208 6.1.
			For clarity, any "redirect" modifier SHOULD appear as the very last
			term in a record.  Any "redirect" modifier MUST be ignored if there
			is an "all" mechanism anywhere in the record.
	*/
	if spf.All == "undefined" {
		if spf.Redirect != nil {
			return spf.Redirect.CheckIP(ip_str)
		}
	}

	// No results found -> check all
	if spf.All != "undefined" {
		return qualifierToResult(spf.All), nil
	}

	return "None", nil
}
func (spf *SPF) CheckIP(ip_str string) (string, error) {
	ip := net.ParseIP(ip_str)
	for _, ip_net := range spf.Fail {
		if ip_net.Contains(ip) {
			return "Fail", nil
		}
	}
	for _, ip_net := range spf.SoftFail {
		if ip_net.Contains(ip) {
			return "SoftFail", nil
		}
	}
	for _, ip_net := range spf.Neutral {
		if ip_net.Contains(ip) {
			return "Neutral", nil
		}
	}
	for _, ip_net := range spf.Pass {
		if ip_net.Contains(ip) {
			return "Pass", nil
		}
	}

	for _, include := range spf.Includes {
		/*
			RFC 7208 5.2
				The "include" mechanism triggers a recursive evaluation of
				check_host().

				1.  The <domain-spec> is expanded as per Section 7.

				2.  check_host() is evaluated with the resulting string as the
					<domain>.  The <ip> and <sender> arguments remain the same as in
					the current evaluation of check_host().

				3.  The recursive evaluation returns match, not-match, or an error.

				4.  If it returns match, then the appropriate result for the
					"include" mechanism is used (e.g., include or +include produces a
					"pass" result and -include produces "fail").

				5.  If it returns not-match or an error, the parent check_host()
					resumes processing as per the table below, with the previous
					value of <domain> restored.

				+---------------------------------+---------------------------------+
				| A recursive check_host() result | Causes the "include" mechanism  |
				| of:                             | to:                             |
				+---------------------------------+---------------------------------+
				| pass                            | match                           |
				|                                 |                                 |
				| fail                            | not match                       |
				|                                 |                                 |
				| softfail                        | not match                       |
				|                                 |                                 |
				| neutral                         | not match                       |
				|                                 |                                 |
				| temperror                       | return temperror                |
				|                                 |                                 |
				| permerror                       | return permerror                |
				|                                 |                                 |
				| none                            | return permerror                |
				+---------------------------------+---------------------------------+
		*/
		check, err := include.spf.CheckIP(ip_str)
		if err != nil {
			return "", nil
		}
		if check == "Pass" {
			return qualifierToResult(include.qualifier), nil
		}
	}

	// Check redirects
	/*
		RFC 7208 6.1.
			For clarity, any "redirect" modifier SHOULD appear as the very last
			term in a record.  Any "redirect" modifier MUST be ignored if there
			is an "all" mechanism anywhere in the record.
	*/
	if spf.All == "undefined" {
		if spf.Redirect != nil {
			return spf.Redirect.CheckIP(ip_str)
		}
	}

	// No results found -> check all
	if spf.All != "undefined" {
		return qualifierToResult(spf.All), nil
	}

	return "None", nil
}
/*
CheckIP checks if the given IP is a valid sender
(returns answers following section 2.6 from RFC 7208)

	result           = "Pass" / "Fail" / "SoftFail" / "Neutral" /
						"None" / "TempError" / "PermError"

	RFC 7208: 2.6.  Results of Evaluation:

	2.6.1.  None

	   A result of "none" means either (a) no syntactically valid DNS domain
	   name was extracted from the SMTP session that could be used as the
	   one to be authorized, or (b) no SPF records were retrieved from
	   the DNS.

	2.6.2.  Neutral

	   A "neutral" result means the ADMD has explicitly stated that it is
	   not asserting whether the IP address is authorized.

	2.6.3.  Pass

	   A "pass" result is an explicit statement that the client is
	   authorized to inject mail with the given identity.

	2.6.4.  Fail

	   A "fail" result is an explicit statement that the client is not
	   authorized to use the domain in the given identity.

	2.6.5.  Softfail

	   A "softfail" result is a weak statement by the publishing ADMD that
	   the host is probably not authorized.  It has not published a
	   stronger, more definitive policy that results in a "fail".

	2.6.6.  Temperror

	   A "temperror" result means the SPF verifier encountered a transient
	   (generally DNS) error while performing the check.  A later retry may
	   succeed without further DNS operator action.

	2.6.7.  Permerror

	   A "permerror" result means the domain's published records could not
	   be correctly interpreted.  This signals an error condition that
	   definitely requires DNS operator intervention to be resolved.
*/