how to do spf record lookup and check in golang?

Viewed 713

I have a requirement to do a domain spf lookup and return PASS / FAIL In case of FAIL I need to return the SPF record for further diagnosis

There seem to be many golang modules on github for SPF check , but they all seem to be unmaintained Also what is the "official" supported module to use

this is my code , please comment

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net"
    "os"

    "github.com/mileusna/spf"
)

func main() {
    domain := os.Args[1]
    log.SetOutput(ioutil.Discard)   // If I dont do this there are a lot of debug logs 
    ip := net.ParseIP("1.2.3.4")
    r := spf.CheckHost(ip, domain, "", "")
    fmt.Printf("Domain = %s\nSPF=%s", domain, r.String())   // I need to return the value , 
                                                           // How do I get the spf record 

}

If I download the github module and make source changes is that a good idea ??

3 Answers

You can refer this article for SPF lookup.

https://ashish.one/gist/spf-lookup-in-go/

In this, It is using this DNS library:

https://github.com/miekg/dns

SPF lookup is nothing but fetching TXT Records and search for string v=spf1 as you mentioned in your question. It is also does the same.

You can use any module whichever fits to your requirement, And you can make changes to it. If you finding your changes will help for others too and it is more generic then you should raised PR (Pull Request) for particular git repo with your changes.

If your changes is more restricted with your requirement only, Then you should use that code for your use only. Better to take fork and you can maintain your own repo with your changes.

If you are not taking any fork and still using original project, Then there will be hard to update the particular library in future.

I thought for a couple of minutes wether to answer this question. The reason for this being that you obviously expect us to read that modules code for you and come up with a solution. Which, to be honest with you, is a bit of a thing considered that you have "requirements", implicating business.

However, others may learn from it and you will have to put enough effort into it to make the code robust enough (IPv6, anyone?).

Basically, you have two questions: "How to get the SPF record for a domain and return it when there is one?" and "How to properly deal with code submissions on github (or git based code hosters in general)?"

How to get the SPF record for a domain, if there is one?

At the end of the day, SPF is a convention utilizing existing technology. An SPF entry is nothing else than a specially formatted TXT resource record in a domain.

Hence, you do not need any package to find our whether a domain has an SPF entry:

package main

import (
    "flag"
    "log"
    "net"
    "os"
    "regexp"
)

var domain string

var spfPattern *regexp.Regexp
var verbose bool

func init() {

    // some flags to make the problem more usable.
    flag.StringVar(&domain, "domain", "", "the domain to check for SPF records")
    flag.BoolVar(&verbose, "verbose", false, "print moar!")

    // Obviously, this is a very, very simple pattern.
    // It lacks any true validation whether the TXT record
    // is in fact a valid SPF. Expanding the regex should
    // not be too hard and I leave it up to you.
    spfPattern = regexp.MustCompile(`\s*v=spf1.*`)
}

func main() {
    flag.Parse()

    // Lookup ALL resource records rs of type TXT for domain.
    rs, err := net.LookupTXT(domain)

    // If there was an error accessing the TXT records...
    if err != nil {
        // ...we inform the user and...
        log.Printf("accessing TXT records for %s", err)
        // ...inform the caller that something went wrong.
        os.Exit(1)
    }

    // Now we have all TXT records for the domain we iterate through them...
    for _, rr := range rs {
        // ... and if one of them matches the pattern of an SPF record...
        if spfPattern.MatchString(rr) {

            // ...we inform the user if the user requested it...
            if verbose {
                log.Printf("%s has a valid SPFv1 record: %s", domain, rr)
            }
            // ...or simply inform the caller, as per the UNIX convention
            // "No news is good news!"
            os.Exit(0)
        }
    }
    // We have iterated through all TXT records and did not find matching
    // the pattern of an SPF record, so we need to inform both the user...
    log.Println("No valid SPF record found.")

    // ...and the caller.
    os.Exit(2)
}

How to properly deal with code contributions

If you see code you think needs expansions, or you found a bug and you want to fix it, the first think you should do is to fork the original repository -- basically, you make a copy of it, with the advantage to retain the commit history.

Next, you clone your fork, and do the work you want to do on that fork. You should use one or more branches for your changes. Every commit should be as atomic as possible. Ideally, the library compiles and the tests run before and after any commit.

When you are done, and you think your code is ready for release, you create a pull request. A pull request basically says "Hey, owner of the original repository, I have made some changes and improved the code, you might want to consider merging my changes into the original code!"

A lot of projects nowadays use a branching model called "gitflow", and I strongly suggest you memorize it until you can sing it backwards with even a severely high BAC.

git flow

I'm the author of package you are using https://github.com/mileusna/spf

Package has function LookupSPF(domain) which will return SPF TXT record, please check the documentation https://pkg.go.dev/github.com/mileusna/spf#LookupSPF

For the maintenance, SPF is implemented according to RFC 7208 which hasn't been updated recently and no one reported any issue with the package, so there is no actuall need for maintenance. :) It just works, at least for my needs.

BTW, I have just added support gor Go modules.

If you have some issue or proposal, please post it on GitHub. :)

Related