add check from ip list
This commit is contained in:
parent
e7db7b9bba
commit
b8cf135c45
|
@ -16,6 +16,7 @@ telebit-client-macos
|
||||||
telebit-client-windows-debug.exe
|
telebit-client-windows-debug.exe
|
||||||
telebit-client-windows.exe
|
telebit-client-windows.exe
|
||||||
|
|
||||||
|
/cmd/iplist/iplist
|
||||||
/cmd/machineid/machineid
|
/cmd/machineid/machineid
|
||||||
/cmd/dnsclient/dnsclient
|
/cmd/dnsclient/dnsclient
|
||||||
/cmd/sqlstore/sqlstore
|
/cmd/sqlstore/sqlstore
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.rootprojects.org/root/telebit/iplist"
|
||||||
|
)
|
||||||
|
|
||||||
|
func help() {
|
||||||
|
fmt.Fprintf(os.Stderr, "Usage: iplist domain.tld 123.45.6.78\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "(`dig TXT +short domain.tld` should return a list like `v=spf1 ip4:123.45.6.78 ip4:123.45.6.1/24`\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if 3 != len(os.Args) {
|
||||||
|
help()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
txtDomain := os.Args[1]
|
||||||
|
remoteIP := net.ParseIP(os.Args[2])
|
||||||
|
if nil == remoteIP {
|
||||||
|
fmt.Fprintf(os.Stderr, "bad remote IP\n")
|
||||||
|
os.Exit(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
iplist.Init(txtDomain)
|
||||||
|
|
||||||
|
allowed, err := iplist.IsAllowed(remoteIP)
|
||||||
|
if nil != err {
|
||||||
|
fmt.Fprintf(os.Stderr, err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !allowed {
|
||||||
|
fmt.Fprintf(os.Stderr, "not allowed\n")
|
||||||
|
os.Exit(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("allowed")
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
package iplist
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var fields []string
|
||||||
|
var initialized bool
|
||||||
|
|
||||||
|
// Init should be called with domain that has valid SPF-like records
|
||||||
|
// to populate the IP whitelist, or with an empty string "" to disable
|
||||||
|
func Init(txtDomain string) []string {
|
||||||
|
initialized = true
|
||||||
|
if "" == txtDomain {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := updateTxt(txtDomain)
|
||||||
|
if nil != err {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
time.Sleep(5 * time.Minute)
|
||||||
|
if err := updateTxt(txtDomain); nil != err {
|
||||||
|
fmt.Fprintf(os.Stderr, "warn: could not update iplist: %s\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for _, section := range fields {
|
||||||
|
parts := strings.Split(section, ":")
|
||||||
|
if 2 != len(parts) || !strings.HasPrefix(parts[0], "ip") {
|
||||||
|
// ignore unsupported bits
|
||||||
|
// (i.e. +mx +ip include:xxx)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ip := parts[1]
|
||||||
|
|
||||||
|
if strings.Contains(ip, "/") {
|
||||||
|
_, _, err := net.ParseCIDR(ip)
|
||||||
|
if nil != err {
|
||||||
|
panic(fmt.Errorf("invalid CIDR %q", ip))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ipAddr := net.ParseIP(ip)
|
||||||
|
if nil == ipAddr {
|
||||||
|
panic(fmt.Errorf(
|
||||||
|
"IP %q from SPF record could not be parsed",
|
||||||
|
ipAddr.String(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateTxt(txtDomain string) error {
|
||||||
|
var newFields []string
|
||||||
|
records, err := net.LookupTXT(txtDomain)
|
||||||
|
if nil != err {
|
||||||
|
return fmt.Errorf("bad spf-domain: %s", err)
|
||||||
|
}
|
||||||
|
for _, record := range records {
|
||||||
|
newFields, err = parseSpf(record)
|
||||||
|
if nil != err {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(fields) > 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO put a lock here?
|
||||||
|
fields = newFields
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAllowed returns true if the given IP matches an IP or CIDR in
|
||||||
|
// the whitelist, or if the spf-domain is an empty string explicitly
|
||||||
|
func IsAllowed(remoteIP net.IP) (bool, error) {
|
||||||
|
if !initialized {
|
||||||
|
panic(fmt.Errorf("was not initialized"))
|
||||||
|
}
|
||||||
|
if 0 == len(fields) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, section := range fields {
|
||||||
|
parts := strings.Split(section, ":")
|
||||||
|
if 2 != len(parts) || !strings.HasPrefix(parts[0], "ip") {
|
||||||
|
// ignore unsupported bits
|
||||||
|
// (i.e. +mx +ip include:xxx)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ip := parts[1]
|
||||||
|
|
||||||
|
if strings.Contains(ip, "/") {
|
||||||
|
_, ipNet, err := net.ParseCIDR(ip)
|
||||||
|
if nil != err {
|
||||||
|
return false, fmt.Errorf("invalid CIDR %q", ip)
|
||||||
|
}
|
||||||
|
return ipNet.Contains(remoteIP), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ipAddr := net.ParseIP(ip)
|
||||||
|
if nil == ipAddr {
|
||||||
|
return false, fmt.Errorf(
|
||||||
|
"IP %q from SPF record could not be parsed",
|
||||||
|
ipAddr.String(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if remoteIP.Equal(ipAddr) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSpf(spf1 string) ([]string, error) {
|
||||||
|
fields := strings.Fields(spf1)
|
||||||
|
if len(fields) < 1 ||
|
||||||
|
len(fields[0]) < 1 ||
|
||||||
|
!strings.HasPrefix(fields[0], "v=") {
|
||||||
|
return nil, errors.New("missing v=")
|
||||||
|
}
|
||||||
|
fields = fields[1:]
|
||||||
|
|
||||||
|
return fields, nil
|
||||||
|
}
|
Loading…
Reference in New Issue