14 Commits

Author SHA1 Message Date
9054c7aff7
feat(httpcache): persist failure time — failed fetches wait MaxAge before retrying 2026-09-08 15:31:46 -06:00
79ff9704f1
feat(net/formmailer): contact-form mailer with cohort filter + STARTTLS
New package handling HTTP POST contact forms: ordered []Field config
(GravityForms-compatible input_N defaults), MaxBytesReader body cap,
geoip+ipcohort gating, X-Forwarded-For trust via TrustedProxies CIDRs,
and SMTP submission over STARTTLS. EHLO LocalName is split from the
NewClient peer host so the client identifies itself correctly while
PlainAuth still binds to the real server name. Hot-reloadable success/
error response bodies via func() []byte. Includes a fake-SMTP integration
test that catches StartTLS(nil) regressions.
2026-09-08 15:28:35 -06:00
f7ca1d22a5
feat(geoip-update): align CLI flags with conventions 2026-09-08 15:19:38 -06:00
e82e3979d2
fix(geoip): update httpcache dependency 2026-09-08 14:47:28 -06:00
05505c3554
fix(geoip-update): back off failed downloads 2026-09-08 14:44:48 -06:00
71f7752015
fix(httpcache): back off after failed fetches 2026-09-08 14:44:48 -06:00
584061744b
feat(geoip-update): CLI for downloading GeoLite2 databases via conditional HTTP GET
- Downloads all editions listed in a GeoIP.conf
  - Flags: -config, -dir, -fresh-days, -base-url (override
    DownloadBase for custom mirror/proxy), -V/--version, -help
  - Version output shows name, version, commit, date, copyright and
    license (populated via ldflags at build time)
  - Uses Basic auth with split credential encoding for readability
  - ETag/Last-Modified caching via httpcache
2026-05-26 22:04:22 -06:00
2f33b5e430
feat(net/geoip): MaxMind GeoLite2 reader with auto-update
Add a Go package for reading MaxMind GeoLite2 City and ASN databases.

  - ParseConf: parses geoipupdate-style config files (AccountID,
    LicenseKey, EditionIDs, DatabaseDirectory)
  - Databases: holds open City + ASN readers; Open() loads both
    editions from a directory, extracting .mmdb from tar.gz in memory
    without writing to disk
  - FindTarGz: resolves cache paths, preferring *_LATEST.tar.gz
  - Lookup: returns city, region, country, ASN, and AS org for an IP
  - DownloadBase constant, TarGzName helper, DefaultConfPaths and
    DefaultCacheDir conveniences

Integration tests verify download and conditional-get freshness.
2026-05-26 22:04:17 -06:00
04b8bb406d
feat(net/httpcache): ETag/Last-Modified HTTP cache
Conditional GET cache for periodic data sources. Persists ETag and
Last-Modified to a .meta sidecar so 304s survive process restarts.
Two independent rate-limit gates (MaxAge file-mtime, MinInterval
in-memory). Body cap via MaxBytes defends against fill-disk from a
hostile upstream or redirect target. Authorization (and any
caller-supplied) headers stripped on every redirect hop. Userinfo
in URL is redacted from error messages.
2026-05-26 01:27:20 -06:00
dddfdd392c
feat(net/ipgate): IP allowlist/blocklist via PrefixSet and DomainSet
PrefixSet: git-backed CIDR files via gitshallow, refreshed every 47 min.
DomainSet: takes pre-parsed (staticPrefixes, domains []string); resolves
hostnames every 5 min, retaining stale IPs on failure. Both use
atomic-swapped ipcohort.Cohort for lock-free reads.

ParseDomainSet(RecordReader) parses rows into prefixes and hostnames;
caller constructs the record reader (e.g. csv.Reader with delimiter,
comment char). RecordReader interface is satisfied by *csv.Reader.

commaify uses plain string concat (integers max ~7 groups).
2026-05-22 12:23:35 -04:00
d472c00ef1
feat(net/dnsresolver): parallel DNS resolver with fallback servers
Resolves A, CNAME chain, and SRV records via miekg/dns. Queries all
configured servers in parallel and returns the first success. Falls
back to OpenDNS/Cloudflare/Quad9 when /etc/resolv.conf is absent or empty.
2026-05-22 11:16:22 -04:00
1db21be047
fix(gitshallow): depth-3 windowed fetch with low-memory GC
- Default depth changed from 1 to 3: enough overlap for delta
  compression even if the remote pushes 2 commits between fetches
- Keep --depth on fetch (not just clone) so shallow stays windowed
- GCInterval default changed: 0 now means every fetch (was disabled)
- GC sequence: reflog expire → prune → gc --prune=now
  Expire makes old shallow entries' objects unreachable, prune removes
  them cheaply (5MB RSS), then gc delta-compresses what remains (~10MB)
- Only GC when HEAD actually changed (skip no-op fetches)
- Dropped GCAggressive (no benefit in shallow clones)
2026-05-11 23:45:10 -06:00
1dacbdd8f7
feat(net/gitshallow): incremental shallow git-repo mirroring
Periodically-updated shallow clone for repos used as a data source.
One Fetch verb (clone-or-pull-or-skip) drives both the Fetcher
interface and per-File handles. MaxAge gates on FETCH_HEAD mtime so
short-lived CLI invocations don't hammer the remote across process
restarts; an in-memory 1s debounce coalesces concurrent Fetches.
Hardens git invocations against argument injection: validates
branch/ref shape, rejects URLs starting with '-', and uses '--'
separators before positional args. Userinfo in URL is redacted from
error output (both args echo and git's own messages).
2026-05-02 16:15:54 -06:00
bea4c3de0a
feat(net/ipcohort): IP block/allow cohort filter
IPv4 host + CIDR membership testing backed by sorted /32 binary search
and a linear scan of CIDR ranges. CSV/text loader with bounded reads
(maxBytes is required so callers pick a limit appropriate to their
input source — 1 KB allowlists through 100+ MB threat feeds). Returns
errors for unparseable IPs so callers can decide fail-open vs
fail-closed; partial cohorts are returned alongside parse errors.
2026-05-02 15:41:38 -06:00