diff --git a/external/cmd/signup/main.go b/external/cmd/signup/main.go index 67e3caa..5daca44 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) @@ -201,9 +244,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))) }),