signup: add DNS checker and run against suspicious hosts #3
62
external/cmd/signup/main.go
vendored
62
external/cmd/signup/main.go
vendored
@ -3,11 +3,15 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"regexp"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -110,6 +114,39 @@ func (c *character) Say(msg string) string {
|
|||||||
strings.TrimSpace(msg))
|
strings.TrimSpace(msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ErrNoSuchDomain = errors.New("no host found for email address")
|
||||||
|
var ErrNoSuchMailserver = errors.New("no mail server found for email address")
|
||||||
|
|
||||||
|
// DigMX does some grubbing around to attempt to find valid email hosts, and
|
||||||
|
// then runs then through [net.LookupMX] and returns their mailserver domains.
|
||||||
|
// may return [ErrNoSuchDomain] or [ErrNoSuchMailserver].
|
||||||
|
func DigMX(raw string) (domains []string, err error) {
|
||||||
|
re := regexp.MustCompile(`@[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+)+\b`) // good enough
|
||||||
|
candidates := re.FindAllString(raw, -1)
|
||||||
|
|
||||||
|
// the error checking tries to be very generous: if anything comes up
|
||||||
|
// positive we will throw no errors and just assume the rest was a fluke.
|
||||||
|
ok := false
|
||||||
|
for _, host := range candidates {
|
||||||
|
records, e := net.LookupMX(host[1:])
|
||||||
|
if e != nil {
|
||||||
|
err = ErrNoSuchDomain
|
||||||
|
} else if len(records) == 0 {
|
||||||
|
err = ErrNoSuchMailserver
|
||||||
|
} else {
|
||||||
|
ok = true
|
||||||
|
for _, record := range records {
|
||||||
|
domains = append(domains, record.Host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
return domains, nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
logFile := path.Join(logDir, fmt.Sprintf("%d", time.Now().Unix()))
|
logFile := path.Join(logDir, fmt.Sprintf("%d", time.Now().Unix()))
|
||||||
logF, err := os.Create(logFile)
|
logF, err := os.Create(logFile)
|
||||||
@ -201,9 +238,32 @@ func _main(l *log.Logger, db *sql.DB) error {
|
|||||||
`),
|
`),
|
||||||
"i'm sorry, before going further could you share an email with me?",
|
"i'm sorry, before going further could you share an email with me?",
|
||||||
newCharacter("wire guy", "a lil homonculus made of discarded computer cables"),
|
newCharacter("wire guy", "a lil homonculus made of discarded computer cables"),
|
||||||
func(s *scene) { su.Email = string(s.Input.Bytes()) },
|
func(s *scene) {
|
||||||
|
su.Email = string(s.Input.Bytes())
|
||||||
|
suspiciousHosts, err := models.SuspiciousHosts(db)
|
||||||
|
if err != nil {
|
||||||
|
// XXX: maybe log somewhere that the database failed
|
||||||
|
|
|||||||
|
return
|
||||||
|
}
|
||||||
|
var shDomains []string
|
||||||
|
for _, host := range suspiciousHosts {
|
||||||
|
shDomains = append(shDomains, host.Domain)
|
||||||
|
}
|
||||||
|
if records, err := DigMX(su.Email); err == nil {
|
||||||
|
for _, record := range records {
|
||||||
|
if slices.Contains(shDomains, record) {
|
||||||
|
su.Notes = append(su.Notes, models.SignupNote{
|
||||||
|
vilmibm
commented
I love using notes for this I love using notes for this
|
|||||||
|
Author: "dns",
|
||||||
|
Content: fmt.Sprintf("email address has suspicious host %s", record),
|
||||||
|
SignupID: su.ID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
func(s *scene, tv *tview.TextView, msg string) {
|
func(s *scene, tv *tview.TextView, msg string) {
|
||||||
// TODO could check and see if it's email shaped and admonish if not
|
// TODO could check and see if it's email shaped and admonish if not
|
||||||
|
// NOTE(nbsp): DigMX call can see if email is invalid but this isn't used yet
|
||||||
|
vilmibm
commented
I predict splitting out the regex from DigMX but it's fine the way it is now. The signing up user should not have any notion of suspicious email hosts but we should gently prod them until we see an email shaped thing I predict splitting out the regex from DigMX but it's fine the way it is now.
The signing up user should not have any notion of suspicious email hosts but we should gently prod them until we see an email shaped thing
|
|||||||
trimmed := strings.TrimSpace(msg)
|
trimmed := strings.TrimSpace(msg)
|
||||||
fmt.Fprintln(tv, s.Host.Say(fmt.Sprintf("I heard '%s'. Is that right? if so, /nod", trimmed)))
|
fmt.Fprintln(tv, s.Host.Say(fmt.Sprintf("I heard '%s'. Is that right? if so, /nod", trimmed)))
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -193,3 +193,35 @@ func (s *TownSignup) All(db *sql.DB) ([]*TownSignup, error) {
|
|||||||
|
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SuspiciousHost struct {
|
||||||
|
ID int64
|
||||||
|
Domain string
|
||||||
|
CommonName string
|
||||||
|
Tier int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func SuspiciousHosts(db *sql.DB) ([]SuspiciousHost, error) {
|
||||||
|
rows, err := db.Query(`SELECT id, domain, common_name, tier FROM suspicious_hosts`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
out := []SuspiciousHost{}
|
||||||
|
for rows.Next() {
|
||||||
|
sh := SuspiciousHost{}
|
||||||
|
if err = rows.Scan(
|
||||||
|
&sh.ID,
|
||||||
|
&sh.Domain,
|
||||||
|
&sh.CommonName,
|
||||||
|
&sh.Tier,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out = append(out, sh)
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -24,3 +24,13 @@ CREATE TABLE IF NOT EXISTS notes (
|
|||||||
|
|
||||||
FOREIGN KEY (signupid) REFERENCES signups(signupid)
|
FOREIGN KEY (signupid) REFERENCES signups(signupid)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- 2025-11-22: bad hosts
|
||||||
|
CREATE TABLE IF NOT EXISTS suspicious_hosts (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
domain TEXT,
|
||||||
|
|
||||||
|
-- unused but worth adding instead of another migration later
|
||||||
|
vilmibm
commented
I find unused stuff more confusing than later need to migrate so I'd leave out unused columns for now I find unused stuff more confusing than later need to migrate so I'd leave out unused columns for now
|
|||||||
|
common_name TEXT,
|
||||||
|
tier INTEGER,
|
||||||
|
)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user
logging good, see lockingwriter package and its use