diff --git a/external/cmd/signup/main.go b/external/cmd/signup/main.go index 67e3caa..ebc8fd8 100644 --- a/external/cmd/signup/main.go +++ b/external/cmd/signup/main.go @@ -3,14 +3,18 @@ package main import ( "bytes" "database/sql" + "errors" "fmt" "io" "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" @@ -21,7 +25,6 @@ import ( const ( maxInputLength = 10000 - logDir = "/town/var/signups/log" ) type scene struct { @@ -110,15 +113,42 @@ func (c *character) Say(msg string) string { strings.TrimSpace(msg)) } -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) +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) + } + } } - logger := log.New(logF, "", log.Ldate|log.Ltime) + if ok { + return domains, nil + } + return +} + +func main() { + 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 { @@ -173,6 +203,28 @@ func _main(l *log.Logger, db *sql.DB) error { su.Created = time.Now() err := su.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 { l.Printf("failed to write to db: %s", err.Error()) l.Printf("dumping values: %#v", su) @@ -201,9 +253,12 @@ 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()) + }, 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))) }), diff --git a/models/models.go b/models/models.go index ffce1c6..defa540 100644 --- a/models/models.go +++ b/models/models.go @@ -193,3 +193,31 @@ func (s *TownSignup) All(db *sql.DB) ([]*TownSignup, error) { return out, nil } + +type SuspiciousHost struct { + ID int64 + Domain string +} + +func SuspiciousHosts(db *sql.DB) ([]SuspiciousHost, error) { + rows, err := db.Query(`SELECT id, domain 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, + ); 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 +)