Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions bimi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

type bimi struct {
bimiset bool
bimifull string
v string
l string // location: SVG logo URI
a string // authority: VMC URI (optional)
}
123 changes: 123 additions & 0 deletions dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,37 @@ func getDMARC(targetHostName string, dnsServer string) (dmarc, error) {
if strings.Contains(v, "v=DMARC1") {
dmarc.dmarcset = true
dmarc.dmarcfull = in.Answer[n].String()
for _, part := range strings.Split(v, ";") {
part = strings.TrimSpace(part)
kv := strings.SplitN(part, "=", 2)
if len(kv) != 2 {
continue
}
switch strings.TrimSpace(kv[0]) {
case "v":
dmarc.v = strings.TrimSpace(kv[1])
case "p":
dmarc.p = strings.TrimSpace(kv[1])
case "sp":
dmarc.sp = strings.TrimSpace(kv[1])
case "adkim":
dmarc.adkim = strings.TrimSpace(kv[1])
case "aspf":
dmarc.aspf = strings.TrimSpace(kv[1])
case "fo":
dmarc.fo = strings.TrimSpace(kv[1])
case "rua":
dmarc.rua = strings.TrimSpace(kv[1])
case "ruf":
dmarc.ruf = strings.TrimSpace(kv[1])
case "rf":
dmarc.rf = strings.TrimSpace(kv[1])
case "ri":
dmarc.ri = strings.TrimSpace(kv[1])
case "pct":
dmarc.pct = strings.TrimSpace(kv[1])
}
}
}
}
}
Expand All @@ -259,3 +290,95 @@ func getDMARC(targetHostName string, dnsServer string) (dmarc, error) {

return dmarc, nil
}

// getTLSRPT builds a TXT request for _smtp._tls.<domain> (RFC 8460)
// and sends it to a dns server.
// It returns type tlsrpt and an error.
func getTLSRPT(targetHostName string, dnsServer string) (tlsrpt, error) {
var result tlsrpt

m := new(dns.Msg)
m.SetQuestion(dns.Fqdn("_smtp._tls."+targetHostName), dns.TypeTXT)

c := new(dns.Client)
c.Net = "tcp"
in, _, err := c.Exchange(m, dnsServer+":53")
if err != nil {
return result, err
}

if len(in.Answer) != 0 {
for n := range in.Answer {
if t, ok := in.Answer[n].(*dns.TXT); ok {
for _, v := range t.Txt {
if strings.HasPrefix(v, "v=TLSRPTv1") {
result.tlsrptset = true
result.tlsrptfull = in.Answer[n].String()
for _, part := range strings.Split(v, ";") {
part = strings.TrimSpace(part)
kv := strings.SplitN(part, "=", 2)
if len(kv) != 2 {
continue
}
switch strings.TrimSpace(kv[0]) {
case "v":
result.v = strings.TrimSpace(kv[1])
case "rua":
result.rua = strings.TrimSpace(kv[1])
}
}
}
}
}
}
}

return result, nil
}

// getBIMI builds a TXT request for default._bimi.<domain>
// and sends it to a dns server.
// It returns type bimi and an error.
func getBIMI(targetHostName string, dnsServer string) (bimi, error) {
var result bimi

m := new(dns.Msg)
m.SetQuestion(dns.Fqdn("default._bimi."+targetHostName), dns.TypeTXT)

c := new(dns.Client)
c.Net = "tcp"
in, _, err := c.Exchange(m, dnsServer+":53")
if err != nil {
return result, err
}

if len(in.Answer) != 0 {
for n := range in.Answer {
if t, ok := in.Answer[n].(*dns.TXT); ok {
for _, v := range t.Txt {
if strings.HasPrefix(v, "v=BIMI1") {
result.bimiset = true
result.bimifull = in.Answer[n].String()
for _, part := range strings.Split(v, ";") {
part = strings.TrimSpace(part)
kv := strings.SplitN(part, "=", 2)
if len(kv) != 2 {
continue
}
switch strings.TrimSpace(kv[0]) {
case "v":
result.v = strings.TrimSpace(kv[1])
case "l":
result.l = strings.TrimSpace(kv[1])
case "a":
result.a = strings.TrimSpace(kv[1])
}
}
}
}
}
}
}

return result, nil
}
80 changes: 76 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ type runresult struct {
bldnsipnotlisted map[string]string
dmarcset bool
dmarcfull string
dmarcresult dmarc
tlsrptresult tlsrpt
bimiresult bimi
}

// mxresult is used to store a mx scan result for further processing
Expand All @@ -50,6 +53,10 @@ type mxresult struct {
starttlsversion string
tlscertvalid bool
tlsversion string
tlscertexpiry time.Time
tlscertsubjectcn string
tlscertissuercn string
tlscertsans []string
smtps bool
openrelay bool
vrfysupport bool
Expand Down Expand Up @@ -181,15 +188,67 @@ func main() {
}
if dmarcentry.dmarcset {
runresult.dmarcset = true
// this copying makes little sense tbh right now. But if we parse the answer it will help.
runresult.dmarcfull = dmarcentry.dmarcfull
runresult.dmarcresult = dmarcentry
InfoLogger.Println(Green("DMARC set"))
InfoLogger.Println(runresult.dmarcfull)
InfoLogger.Println("DMARC Policy: " + dmarcentry.p)
if len(dmarcentry.sp) > 0 {
InfoLogger.Println("DMARC Subdomain Policy: " + dmarcentry.sp)
}
if len(dmarcentry.adkim) > 0 {
InfoLogger.Println("DMARC DKIM Alignment: " + dmarcentry.adkim)
}
if len(dmarcentry.aspf) > 0 {
InfoLogger.Println("DMARC SPF Alignment: " + dmarcentry.aspf)
}
if len(dmarcentry.fo) > 0 {
InfoLogger.Println("DMARC Failure Options: " + dmarcentry.fo)
}
if len(dmarcentry.rua) > 0 {
InfoLogger.Println("DMARC RUA: " + dmarcentry.rua)
}
if len(dmarcentry.ruf) > 0 {
InfoLogger.Println("DMARC RUF: " + dmarcentry.ruf)
}
if len(dmarcentry.pct) > 0 {
InfoLogger.Println("DMARC Pct: " + dmarcentry.pct)
}
} else {
// This is just yellow because DMARC has its friends and foes...
InfoLogger.Println(Yellow("No DMARC set"))
}

// TLSRPT lookup
InfoLogger.Println("== Checking TLSRPT record ==")
tlsrptentry, err := getTLSRPT(*targetHostName, *dnsServer)
if err != nil {
ErrorLogger.Println(err.Error())
}
runresult.tlsrptresult = tlsrptentry
if tlsrptentry.tlsrptset {
InfoLogger.Println(Green("TLSRPT set"))
InfoLogger.Println("TLSRPT RUA: " + tlsrptentry.rua)
} else {
InfoLogger.Println(Yellow("No TLSRPT set"))
}

// BIMI lookup
InfoLogger.Println("== Checking BIMI record ==")
bimientry, err := getBIMI(*targetHostName, *dnsServer)
if err != nil {
ErrorLogger.Println(err.Error())
}
runresult.bimiresult = bimientry
if bimientry.bimiset {
InfoLogger.Println(Green("BIMI set"))
InfoLogger.Println("BIMI Logo URI: " + bimientry.l)
if len(bimientry.a) > 0 {
InfoLogger.Println("BIMI Authority URI: " + bimientry.a)
}
} else {
InfoLogger.Println(Yellow("No BIMI set"))
}

// Check blacklists for domain name
// InfoLogger.Println("Checking if domain is blacklisted")
if *blacklist {
Expand Down Expand Up @@ -400,11 +459,18 @@ func main() {
// TLS test
if port == "465" {
InfoLogger.Println("== Checking for TLS support on port " + port + " ==")
orresult.tlsversion, orresult.tlsbool, orresult.tlsvalid, err = tlsCheck(targetHost, port)
certinfo, err := tlsCheck(targetHost, port)
if err != nil {
InfoLogger.Println(err)
}
singlemx.tlsversion = orresult.tlsversion
orresult.tlsbool = certinfo.tlsok
orresult.tlsvalid = certinfo.certvalid
orresult.tlsversion = certinfo.version
singlemx.tlsversion = certinfo.version
singlemx.tlscertexpiry = certinfo.expiry
singlemx.tlscertsubjectcn = certinfo.subjectCN
singlemx.tlscertissuercn = certinfo.issuerCN
singlemx.tlscertsans = certinfo.sans
if orresult.tlsbool {
InfoLogger.Println(Green("SMTPS supported"))
if orresult.tlsvalid {
Expand All @@ -419,6 +485,12 @@ func main() {
} else {
InfoLogger.Println("SMTPS TLS Version: " + orresult.tlsversion)
}
if !certinfo.expiry.IsZero() {
InfoLogger.Println("SMTPS Cert Expiry: " + certinfo.expiry.Format(time.RFC3339))
InfoLogger.Println("SMTPS Cert Subject CN: " + certinfo.subjectCN)
InfoLogger.Println("SMTPS Cert Issuer CN: " + certinfo.issuerCN)
InfoLogger.Println("SMTPS Cert SANs: " + strings.Join(certinfo.sans, ", "))
}
} else {
InfoLogger.Println(Cyan("SMTPS not supported"))
}
Expand Down
77 changes: 75 additions & 2 deletions output.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"slices"
"strconv"
"strings"
"time"
)

Expand Down Expand Up @@ -74,7 +75,65 @@ func writeTSV(targetHostName string, runresult runresult, blacklist bool) error
return err
}
if runresult.dmarcset {
if err = tsv.Write([]string{"DMARC Entry", runresult.dmarcfull}); err != nil {
d := runresult.dmarcresult
if err = tsv.Write([]string{"DMARC Full Entry", d.dmarcfull}); err != nil {
return err
}
if err = tsv.Write([]string{"DMARC Version", d.v}); err != nil {
return err
}
if err = tsv.Write([]string{"DMARC Policy", d.p}); err != nil {
return err
}
if err = tsv.Write([]string{"DMARC Subdomain Policy", d.sp}); err != nil {
return err
}
if err = tsv.Write([]string{"DMARC DKIM Alignment", d.adkim}); err != nil {
return err
}
if err = tsv.Write([]string{"DMARC SPF Alignment", d.aspf}); err != nil {
return err
}
if err = tsv.Write([]string{"DMARC Failure Options", d.fo}); err != nil {
return err
}
if err = tsv.Write([]string{"DMARC RUA", d.rua}); err != nil {
return err
}
if err = tsv.Write([]string{"DMARC RUF", d.ruf}); err != nil {
return err
}
if err = tsv.Write([]string{"DMARC Pct", d.pct}); err != nil {
return err
}
if err = tsv.Write([]string{"DMARC RI", d.ri}); err != nil {
return err
}
}

if err = tsv.Write([]string{"TLSRPT Set", strconv.FormatBool(runresult.tlsrptresult.tlsrptset)}); err != nil {
return err
}
if runresult.tlsrptresult.tlsrptset {
if err = tsv.Write([]string{"TLSRPT RUA", runresult.tlsrptresult.rua}); err != nil {
return err
}
if err = tsv.Write([]string{"TLSRPT Full Entry", runresult.tlsrptresult.tlsrptfull}); err != nil {
return err
}
}

if err = tsv.Write([]string{"BIMI Set", strconv.FormatBool(runresult.bimiresult.bimiset)}); err != nil {
return err
}
if runresult.bimiresult.bimiset {
if err = tsv.Write([]string{"BIMI Logo URI", runresult.bimiresult.l}); err != nil {
return err
}
if err = tsv.Write([]string{"BIMI Authority URI", runresult.bimiresult.a}); err != nil {
return err
}
if err = tsv.Write([]string{"BIMI Full Entry", runresult.bimiresult.bimifull}); err != nil {
return err
}
}
Expand Down Expand Up @@ -127,9 +186,23 @@ func writeTSV(targetHostName string, runresult runresult, blacklist bool) error
return err
}
if slices.Contains(mxentry.openports, "465") {
if err = tsv.Write([]string{"TLS Version PORT 465 ", mxentry.tlsversion}); err != nil {
if err = tsv.Write([]string{"TLS Version PORT 465", mxentry.tlsversion}); err != nil {
return err
}
if !mxentry.tlscertexpiry.IsZero() {
if err = tsv.Write([]string{"TLS Cert Expiry", mxentry.tlscertexpiry.Format(time.RFC3339)}); err != nil {
return err
}
if err = tsv.Write([]string{"TLS Cert Subject CN", mxentry.tlscertsubjectcn}); err != nil {
return err
}
if err = tsv.Write([]string{"TLS Cert Issuer CN", mxentry.tlscertissuercn}); err != nil {
return err
}
if err = tsv.Write([]string{"TLS Cert SANs", strings.Join(mxentry.tlscertsans, ",")}); err != nil {
return err
}
}
}
if err = tsv.Write([]string{"VRFY Supported", strconv.FormatBool(mxentry.vrfysupport)}); err != nil {
return err
Expand Down
Loading
Loading