Merge pull request 'signup: add DNS checker and run against suspicious hosts' (#3) from nbsp/town:suspicious-hosts into trunk

Reviewed-on: #3
Reviewed-by: vilmibm <vilmibm@noreply.example.org>
This commit is contained in:
vilmibm 2025-12-31 00:06:42 +00:00
commit ee74f26d6f
3 changed files with 99 additions and 10 deletions

View File

@ -3,14 +3,18 @@ package main
import ( import (
"bytes" "bytes"
"database/sql" "database/sql"
"errors"
"fmt" "fmt"
"io" "io"
"log" "log"
"net"
"os" "os"
"path" "regexp"
"slices"
"strings" "strings"
"time" "time"
"git.tilde.town/tildetown/town/external/lockingwriter"
"git.tilde.town/tildetown/town/models" "git.tilde.town/tildetown/town/models"
"git.tilde.town/tildetown/town/signup" "git.tilde.town/tildetown/town/signup"
"github.com/MakeNowJust/heredoc/v2" "github.com/MakeNowJust/heredoc/v2"
@ -21,7 +25,6 @@ import (
const ( const (
maxInputLength = 10000 maxInputLength = 10000
logDir = "/town/var/signups/log"
) )
type scene struct { type scene struct {
@ -110,15 +113,42 @@ func (c *character) Say(msg string) string {
strings.TrimSpace(msg)) strings.TrimSpace(msg))
} }
func main() { var ErrNoSuchDomain = errors.New("no host found for email address")
logFile := path.Join(logDir, fmt.Sprintf("%d", time.Now().Unix())) var ErrNoSuchMailserver = errors.New("no mail server found for email address")
logF, err := os.Create(logFile)
if err != nil { // DigMX does some grubbing around to attempt to find valid email hosts, and
fmt.Fprintln(os.Stderr, err) // then runs then through [net.LookupMX] and returns their mailserver domains.
os.Exit(1) // 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() db, err := signup.ConnectDB()
if err != nil { if err != nil {
@ -173,6 +203,28 @@ func _main(l *log.Logger, db *sql.DB) error {
su.Created = time.Now() su.Created = time.Now()
err := su.Insert(db) 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 { if err != nil {
l.Printf("failed to write to db: %s", err.Error()) l.Printf("failed to write to db: %s", err.Error())
l.Printf("dumping values: %#v", su) 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?", "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())
},
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
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)))
}), }),

View File

@ -193,3 +193,31 @@ func (s *TownSignup) All(db *sql.DB) ([]*TownSignup, error) {
return out, nil 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
}

View File

@ -24,3 +24,9 @@ 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
)