Files
mailgoserver/internal/db/crud_domains.go
T

198 lines
6.8 KiB
Go

package db
import (
"crypto/rand"
"database/sql"
"encoding/hex"
"time"
)
func (d *DB) ListDomains() ([]Domain, error) {
rows, err := d.Query(`SELECT ` + domainColumns + ` FROM esrv_domains ORDER BY domain_name`)
if err != nil {
return nil, err
}
defer rows.Close()
var out []Domain
for rows.Next() {
var dm Domain
var createdAt string
var verifiedAt *string
var catchall sql.NullInt64
var rateLimit sql.NullInt64
if err := rows.Scan(&dm.ID, &dm.DomainName, &dm.IsActive, &createdAt, &dm.VerificationToken, &dm.IsVerified, &verifiedAt, &dm.MFAExempt, &catchall, &rateLimit, &dm.MTASTSMode, &dm.CalDAVEnabled, &dm.CardDAVEnabled, &dm.DKIMDNSAutomation, &dm.UseGlobalDKIM); err != nil {
return nil, err
}
dm.CreatedAt, _ = parseTime(createdAt)
if verifiedAt != nil {
t, _ := parseTime(*verifiedAt)
dm.VerifiedAt = &t
}
if catchall.Valid {
dm.CatchallMailboxID = &catchall.Int64
}
if rateLimit.Valid {
n := int(rateLimit.Int64)
dm.SendRateLimitPerHour = &n
}
out = append(out, dm)
}
return out, rows.Err()
}
// SetDomainCatchall sets (or clears, with a nil mailboxID) the domain's opt-in
// catch-all fallback mailbox — see mailstore.ResolveRecipient.
func (d *DB) SetDomainCatchall(domainID int64, mailboxID *int64) error {
_, err := d.Exec(`UPDATE esrv_domains SET catchall_mailbox_id = ? WHERE id = ?`, mailboxID, domainID)
return err
}
// SetDomainSendRateLimit sets (or clears, with a nil limit) the domain's outbound
// send-rate cap — see schema.go's send_rate_limit_per_hour column comment.
func (d *DB) SetDomainSendRateLimit(domainID int64, limit *int) error {
_, err := d.Exec(`UPDATE esrv_domains SET send_rate_limit_per_hour = ? WHERE id = ?`, limit, domainID)
return err
}
// SetDomainMTASTSMode sets the domain's served MTA-STS policy mode ("testing" or
// "enforce") — see schema.go's mta_sts_mode column comment.
func (d *DB) SetDomainMTASTSMode(domainID int64, mode string) error {
_, err := d.Exec(`UPDATE esrv_domains SET mta_sts_mode = ? WHERE id = ?`, mode, domainID)
return err
}
// SetDomainCalDAVEnabled/SetDomainCardDAVEnabled toggle this domain's master switch for
// each protocol — see schema.go's caldav_enabled/carddav_enabled column comments.
func (d *DB) SetDomainCalDAVEnabled(domainID int64, enabled bool) error {
_, err := d.Exec(`UPDATE esrv_domains SET caldav_enabled = ? WHERE id = ?`, enabled, domainID)
return err
}
func (d *DB) SetDomainCardDAVEnabled(domainID int64, enabled bool) error {
_, err := d.Exec(`UPDATE esrv_domains SET carddav_enabled = ? WHERE id = ?`, enabled, domainID)
return err
}
// SetDomainDKIMDNSAutomation sets this domain's DKIM DNS-publish mode — "manual"
// (default) or "automatic". Not validated here — callers restrict to those two values
// (internal/webui/dkim.go).
func (d *DB) SetDomainDKIMDNSAutomation(domainID int64, mode string) error {
_, err := d.Exec(`UPDATE esrv_domains SET dkim_dns_automation = ? WHERE id = ?`, mode, domainID)
return err
}
// SetDomainUseGlobalDKIM opts this domain in or out of signing with the shared/global
// DKIM key — see schema.go's use_global_dkim column comment.
func (d *DB) SetDomainUseGlobalDKIM(domainID int64, use bool) error {
_, err := d.Exec(`UPDATE esrv_domains SET use_global_dkim = ? WHERE id = ?`, use, domainID)
return err
}
// ListActiveDomains mirrors the `domains` query used to populate <select> lists on the
// add/edit sender and IP forms.
func (d *DB) ListActiveDomains() ([]Domain, error) {
all, err := d.ListDomains()
if err != nil {
return nil, err
}
var out []Domain
for _, dm := range all {
if dm.IsActive {
out = append(out, dm)
}
}
return out, nil
}
func (d *DB) CountActiveDomains() (int, error) {
var n int
err := d.QueryRow(`SELECT COUNT(*) FROM esrv_domains WHERE is_active = 1`).Scan(&n)
return n, err
}
// generateVerificationToken returns a random 32-hex-char token for the DNS TXT
// ownership check, mirroring the randomness quality already used for DKIM selectors.
func generateVerificationToken() string {
b := make([]byte, 16)
rand.Read(b)
return hex.EncodeToString(b)
}
// CreateDomain inserts a new, unverified domain with a freshly generated DNS
// verification token.
func (d *DB) CreateDomain(name string) (int64, error) {
res, err := d.Exec(`INSERT INTO esrv_domains (domain_name, is_active, verification_token, is_verified) VALUES (?, 1, ?, 0)`,
name, generateVerificationToken())
if err != nil {
return 0, err
}
return res.LastInsertId()
}
func (d *DB) UpdateDomain(id int64, name string, requiresAuth bool) error {
_, err := d.Exec(`UPDATE esrv_domains SET domain_name = ? WHERE id = ?`, name, id)
return err
}
func (d *DB) SetDomainActive(id int64, active bool) error {
_, err := d.Exec(`UPDATE esrv_domains SET is_active = ? WHERE id = ?`, active, id)
return err
}
// SetDomainMFAExempt overrides [Auth] enforce_mailbox_mfa off for every mailbox under
// this domain (regardless of each mailbox's own MFAExempt).
func (d *DB) SetDomainMFAExempt(id int64, exempt bool) error {
_, err := d.Exec(`UPDATE esrv_domains SET mfa_exempt = ? WHERE id = ?`, exempt, id)
return err
}
// SetDomainVerified mirrors marking a domain as DNS-ownership-verified (or reverting
// it, e.g. if an admin wants to force re-verification).
func (d *DB) SetDomainVerified(id int64, verified bool) error {
if verified {
_, err := d.Exec(`UPDATE esrv_domains SET is_verified = 1, verified_at = ? WHERE id = ?`, time.Now(), id)
return err
}
_, err := d.Exec(`UPDATE esrv_domains SET is_verified = 0, verified_at = NULL WHERE id = ?`, id)
return err
}
// RegenerateVerificationToken mirrors resetting a domain back to a fresh, unverified
// token — used if an admin wants a new TXT value (e.g. suspected leak, or restarting
// the ownership proof).
func (d *DB) RegenerateVerificationToken(id int64) (string, error) {
token := generateVerificationToken()
_, err := d.Exec(`UPDATE esrv_domains SET verification_token = ?, is_verified = 0, verified_at = NULL WHERE id = ?`, token, id)
return token, err
}
// RemoveDomainCascade hard-deletes a domain and every row that references it, mirroring
// domains.remove_domain. Returns counts removed for the flash message.
func (d *DB) RemoveDomainCascade(id int64) (senders, ips, dkimKeys, headers int, err error) {
tx, err := d.Begin()
if err != nil {
return
}
defer tx.Rollback()
for table, count := range map[string]*int{
"esrv_senders": &senders,
"esrv_whitelisted_ips": &ips,
"esrv_dkim_keys": &dkimKeys,
"esrv_custom_headers": &headers,
} {
row := tx.QueryRow(`SELECT COUNT(*) FROM `+table+` WHERE domain_id = ?`, id)
if err = row.Scan(count); err != nil {
return
}
if _, err = tx.Exec(`DELETE FROM `+table+` WHERE domain_id = ?`, id); err != nil {
return
}
}
if _, err = tx.Exec(`DELETE FROM esrv_domains WHERE id = ?`, id); err != nil {
return
}
err = tx.Commit()
return
}