From 982b705fb28c9a46ec5208f126f3c5a235e7dd21 Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Sat, 22 Nov 2025 02:35:59 +0100 Subject: [PATCH 1/3] signup: add DNS checker and run against suspicious hosts --- external/cmd/signup/main.go | 64 ++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/external/cmd/signup/main.go b/external/cmd/signup/main.go index 67e3caa..e6aa40e 100644 --- a/external/cmd/signup/main.go +++ b/external/cmd/signup/main.go @@ -3,11 +3,15 @@ package main import ( "bytes" "database/sql" + "errors" "fmt" "io" "log" + "net" "os" "path" + "regexp" + "slices" "strings" "time" @@ -110,6 +114,45 @@ func (c *character) Say(msg string) string { strings.TrimSpace(msg)) } +// TODO: move this into an admin-editable world-unreadable file somewhere +var suspiciousHosts = []string{ + "mx1.cock.li", + "mx2.cock.li", +} + +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() { logFile := path.Join(logDir, fmt.Sprintf("%d", time.Now().Unix())) logF, err := os.Create(logFile) @@ -173,6 +216,11 @@ func _main(l *log.Logger, db *sql.DB) error { su.Created = time.Now() err := su.Insert(db) + for _, note := range su.Notes { + note.SignupID = su.ID + err = note.Insert(db) + } + if err != nil { l.Printf("failed to write to db: %s", err.Error()) l.Printf("dumping values: %#v", su) @@ -201,9 +249,23 @@ func _main(l *log.Logger, db *sql.DB) error { `), "i'm sorry, before going further could you share an email with me?", 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()) + if records, err := DigMX(su.Email); err != nil { + for _, record := range records { + if slices.Contains(suspiciousHosts, record) { + su.Notes = append(su.Notes, models.SignupNote{ + Author: "dns", + Content: fmt.Sprintf("email address has suspicious host %s", record), + SignupID: su.ID, + }) + } + } + } + }, func(s *scene, tv *tview.TextView, msg string) { // 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 trimmed := strings.TrimSpace(msg) fmt.Fprintln(tv, s.Host.Say(fmt.Sprintf("I heard '%s'. Is that right? if so, /nod", trimmed))) }), From 544a1f6ebd32d9f6e6d57a447ebec8cbea3d972c Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Sat, 22 Nov 2025 12:41:11 +0100 Subject: [PATCH 2/3] add suspicious_hosts table to signups.sql --- external/cmd/signup/main.go | 32 ++++++++++++++------------------ models/models.go | 32 ++++++++++++++++++++++++++++++++ sql/create_signups_db.sql | 6 ++++++ 3 files changed, 52 insertions(+), 18 deletions(-) diff --git a/external/cmd/signup/main.go b/external/cmd/signup/main.go index e6aa40e..16af07a 100644 --- a/external/cmd/signup/main.go +++ b/external/cmd/signup/main.go @@ -9,12 +9,12 @@ import ( "log" "net" "os" - "path" "regexp" "slices" "strings" "time" + "git.tilde.town/tildetown/town/external/lockingwriter" "git.tilde.town/tildetown/town/models" "git.tilde.town/tildetown/town/signup" "github.com/MakeNowJust/heredoc/v2" @@ -25,7 +25,6 @@ import ( const ( maxInputLength = 10000 - logDir = "/town/var/signups/log" ) type scene struct { @@ -114,12 +113,6 @@ func (c *character) Say(msg string) string { strings.TrimSpace(msg)) } -// TODO: move this into an admin-editable world-unreadable file somewhere -var suspiciousHosts = []string{ - "mx1.cock.li", - "mx2.cock.li", -} - var ErrNoSuchDomain = errors.New("no host found for email address") var ErrNoSuchMailserver = errors.New("no mail server found for email address") @@ -154,14 +147,8 @@ func DigMX(raw string) (domains []string, err error) { } func main() { - logFile := path.Join(logDir, fmt.Sprintf("%d", time.Now().Unix())) - logF, err := os.Create(logFile) - if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - - logger := log.New(logF, "", log.Ldate|log.Ltime) + lw := lockingwriter.New() + logger := log.New(lw, "signup: ", log.Ldate|log.Ltime|log.LUTC|log.Lshortfile|log.Lmsgprefix) db, err := signup.ConnectDB() if err != nil { @@ -251,9 +238,18 @@ func _main(l *log.Logger, db *sql.DB) error { newCharacter("wire guy", "a lil homonculus made of discarded computer cables"), func(s *scene) { su.Email = string(s.Input.Bytes()) - if records, err := DigMX(su.Email); err != nil { + suspiciousHosts, err := models.SuspiciousHosts(db) + if err != nil { + l.Println("could not connect to suspicious hosts db") + 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(suspiciousHosts, record) { + if slices.Contains(shDomains, record) { su.Notes = append(su.Notes, models.SignupNote{ Author: "dns", Content: fmt.Sprintf("email address has suspicious host %s", record), diff --git a/models/models.go b/models/models.go index ffce1c6..a1b9b35 100644 --- a/models/models.go +++ b/models/models.go @@ -193,3 +193,35 @@ func (s *TownSignup) All(db *sql.DB) ([]*TownSignup, error) { 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 +} diff --git a/sql/create_signups_db.sql b/sql/create_signups_db.sql index b32f8eb..befd839 100644 --- a/sql/create_signups_db.sql +++ b/sql/create_signups_db.sql @@ -24,3 +24,9 @@ CREATE TABLE IF NOT EXISTS notes ( FOREIGN KEY (signupid) REFERENCES signups(signupid) ); + +-- 2025-11-22: bad hosts +CREATE TABLE IF NOT EXISTS suspicious_hosts ( + id INTEGER PRIMARY KEY, + domain TEXT +) From ede5d0e43039a3cbfecea063e0d471936ab3b81d Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Wed, 31 Dec 2025 01:02:39 +0100 Subject: [PATCH 3/3] move to save step --- external/cmd/signup/main.go | 43 +++++++++++++++++-------------------- models/models.go | 10 +++------ 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/external/cmd/signup/main.go b/external/cmd/signup/main.go index 16af07a..ebc8fd8 100644 --- a/external/cmd/signup/main.go +++ b/external/cmd/signup/main.go @@ -203,9 +203,26 @@ func _main(l *log.Logger, db *sql.DB) error { su.Created = time.Now() err := su.Insert(db) - for _, note := range su.Notes { - note.SignupID = su.ID - err = note.Insert(db) + suspiciousHosts, err := models.SuspiciousHosts(db) + if err != nil { + l.Println("could not connect to suspicious hosts db:", err) + 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) { + note := models.SignupNote{ + Author: "dns", + Content: fmt.Sprintf("email address has suspicious host %s", record), + SignupID: su.ID, + } + err = note.Insert(db) + } + } } if err != nil { @@ -238,26 +255,6 @@ func _main(l *log.Logger, db *sql.DB) error { newCharacter("wire guy", "a lil homonculus made of discarded computer cables"), func(s *scene) { su.Email = string(s.Input.Bytes()) - suspiciousHosts, err := models.SuspiciousHosts(db) - if err != nil { - l.Println("could not connect to suspicious hosts db") - 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{ - Author: "dns", - Content: fmt.Sprintf("email address has suspicious host %s", record), - SignupID: su.ID, - }) - } - } - } }, func(s *scene, tv *tview.TextView, msg string) { // TODO could check and see if it's email shaped and admonish if not diff --git a/models/models.go b/models/models.go index a1b9b35..defa540 100644 --- a/models/models.go +++ b/models/models.go @@ -195,14 +195,12 @@ func (s *TownSignup) All(db *sql.DB) ([]*TownSignup, error) { } type SuspiciousHost struct { - ID int64 - Domain string - CommonName string - Tier int64 + ID int64 + Domain string } func SuspiciousHosts(db *sql.DB) ([]SuspiciousHost, error) { - rows, err := db.Query(`SELECT id, domain, common_name, tier FROM suspicious_hosts`) + rows, err := db.Query(`SELECT id, domain FROM suspicious_hosts`) if err != nil { return nil, err } @@ -214,8 +212,6 @@ func SuspiciousHosts(db *sql.DB) ([]SuspiciousHost, error) { if err = rows.Scan( &sh.ID, &sh.Domain, - &sh.CommonName, - &sh.Tier, ); err != nil { return nil, err }