all repos — postern

@ 087f9dd30bc5a591621f1184af323569d6a0d240

Modern mail management

clone
git clone https:///postern.git

internal/dkim/dkim.go (view raw)

  1package dkim
  2
  3import (
  4	"bytes"
  5	"context"
  6	"database/sql"
  7	"errors"
  8	"fmt"
  9	"strings"
 10
 11	"postern/internal/db"
 12	"postern/internal/model"
 13	"postern/internal/persistence"
 14)
 15
 16type Config struct {
 17	DB          *db.DB
 18	Persistence *persistence.Persistence
 19}
 20
 21type DKIM struct {
 22	db          *db.DB
 23	persistence *persistence.Persistence
 24}
 25
 26func New(config *Config) *DKIM {
 27	return &DKIM{
 28		db:          config.DB,
 29		persistence: config.Persistence,
 30	}
 31}
 32
 33// Add generates a new RSA-2048 private key for the given selector/domain, stores it
 34// as a blob, inserts the corresponding row in the database and returns the expected
 35// DNS TXT record that should be published before enabling the selector.
 36func (d *DKIM) Add(ctx context.Context, selector, domain string) (string, error) {
 37	if selector == "" {
 38		return "", fmt.Errorf("invalid selector")
 39	}
 40	if strings.Contains(selector, ".") {
 41		return "", fmt.Errorf("selector must not contain '.'")
 42	}
 43	if domain == "" {
 44		return "", fmt.Errorf("invalid domain")
 45	}
 46
 47	privKey, keyType := genRSAPrivateKey()
 48	encodedPrivKey := encodePrivateKey(privKey)
 49
 50	blobName, _, err := d.persistence.WriteBlob(bytes.NewReader(encodedPrivKey))
 51	if err != nil {
 52		return "", err
 53	}
 54
 55	err = d.db.InsertDKIMForDomain(ctx, model.DKIM{
 56		Domain:      domain,
 57		Selector:    selector,
 58		BlobAddress: blobName,
 59		KeyType:     keyType,
 60		Enabled:     false,
 61	})
 62	if err != nil {
 63		return "", err
 64	}
 65
 66	return pubKeyRecord(privKey.Public()), nil
 67}
 68
 69// List returns all configured DKIM selectors.
 70func (d *DKIM) List(ctx context.Context) ([]model.DKIM, error) {
 71	return d.db.ListDKIM(ctx)
 72}
 73
 74// PublicDNSRecord returns the expected DNS TXT record for the given selector/domain
 75// as it should be published under selector._domainkey.domain.
 76func (d *DKIM) PublicDNSRecord(ctx context.Context, selector, domain string) (string, error) {
 77	privKey, _, err := d.retrievePrivateKey(ctx, selector, domain)
 78	if err != nil {
 79		return "", err
 80	}
 81	return pubKeyRecord(privKey.Public()), nil
 82}
 83
 84// SetEnabled enables or disables the given selector/domain selector.
 85func (d *DKIM) SetEnabled(ctx context.Context, selector, domain string, enabled bool) error {
 86	return d.db.SetDKIMEnabled(ctx, selector, domain, enabled)
 87}
 88
 89// Remove deletes the private key and the database entry.
 90func (d *DKIM) Remove(ctx context.Context, selector, domain string) error {
 91	dkimRow, err := d.db.GetDKIMForSelectorDomain(ctx, selector, domain)
 92	if err != nil {
 93		if errors.Is(err, sql.ErrNoRows) {
 94			return errors.New("DKIM entry not found")
 95		}
 96		return err
 97	}
 98
 99	tx, err := d.db.GetWriteTx(ctx)
100	if err != nil {
101		return err
102	}
103
104	err = db.RemoveDKIM(ctx, tx, selector, domain)
105	if err != nil {
106		_ = tx.Rollback()
107		return err
108	}
109
110	err = d.persistence.RemoveBlob(dkimRow.BlobAddress)
111	if err != nil {
112		_ = tx.Rollback()
113		return err
114	}
115
116	return tx.Commit()
117}