11 cb474497 2022-04-19 o // An Alias is used by a server to forward mail it receives.
12 cb474497 2022-04-19 o // Mail addressed to Recipient is forwarded to Destination.
13 925168e0 2022-04-14 o type Alias struct {
14 71bdede3 2022-04-21 o // Recipient is just the username part of a publicly visible email address.
15 71bdede3 2022-04-21 o // The domain(s) available for accepting mail depend on the SMTP server
16 cb474497 2022-04-19 o // implementation.
17 71bdede3 2022-04-21 o Recipient string
18 cb474497 2022-04-19 o // Destination contains an email address, with domain suffix,
19 cb474497 2022-04-19 o // to which mail will be forwarded.
20 925168e0 2022-04-14 o Destination string
21 cb474497 2022-04-19 o // Expiry specifies a time after which the alias is considered inactive;
22 cb474497 2022-04-19 o // that is, mail addressed to Recipient should be bounced.
23 71bdede3 2022-04-21 o Expiry time.Time
24 cb474497 2022-04-19 o // Note contains user-defined text that can be used to,
25 cb474497 2022-04-19 o // for example, identify the alias.
29 925168e0 2022-04-14 o type AliasStore interface {
30 925168e0 2022-04-14 o Create(dest string) (Alias, error)
31 71bdede3 2022-04-21 o Put(alias Alias) error
32 925168e0 2022-04-14 o Aliases(dest string) ([]Alias, error)
33 925168e0 2022-04-14 o Delete(rcpt string) error
36 cb474497 2022-04-19 o // AliasDB is an implementation of AliasStore backed by a sqlite3 database.
37 925168e0 2022-04-14 o type AliasDB struct {
39 925168e0 2022-04-14 o dictpath string
42 cb474497 2022-04-19 o var errRecipientNotExist = errors.New("no such recipient")
44 cb474497 2022-04-19 o // OpenAliasDB opens the named database file, using the file at dictpath for
45 71bdede3 2022-04-21 o // generating recipient names for new aliases. The database is created and
46 cb474497 2022-04-19 o // initialised if it doesn't already exist.
47 925168e0 2022-04-14 o func OpenAliasDB(name, dictpath string) (*AliasDB, error) {
48 925168e0 2022-04-14 o db, err := sql.Open("sqlite3", name)
49 925168e0 2022-04-14 o if err != nil {
50 925168e0 2022-04-14 o return nil, err
53 925168e0 2022-04-14 o CREATE TABLE IF NOT EXISTS aliases (
54 925168e0 2022-04-14 o recipient TEXT PRIMARY KEY,
55 925168e0 2022-04-14 o destination TEXT NOT NULL,
56 cb474497 2022-04-19 o expiry INTEGER NOT NULL,
57 cb474497 2022-04-19 o note TEXT NOT NULL
59 925168e0 2022-04-14 o _, err = db.Exec(stmt)
60 052981c3 2022-04-16 o if err != nil {
61 052981c3 2022-04-16 o return nil, err
63 052981c3 2022-04-16 o _, err = os.Stat(dictpath)
64 925168e0 2022-04-14 o return &AliasDB{db, dictpath}, err
67 cb474497 2022-04-19 o // Create is a convenience method for creating a new random alias for destination.
68 cb474497 2022-04-19 o // To set more Alias attributes, such as setting an expiry date, use Put.
69 cb474497 2022-04-19 o func (db *AliasDB) Create(destination string) (Alias, error) {
70 925168e0 2022-04-14 o rcpt, err := RandomUsername(db.dictpath)
71 925168e0 2022-04-14 o if err != nil {
72 925168e0 2022-04-14 o return Alias{}, fmt.Errorf("create alias: %w", err)
75 cb474497 2022-04-19 o Recipient: rcpt,
76 cb474497 2022-04-19 o Destination: destination,
78 cb474497 2022-04-19 o if err := db.Put(a); err != nil {
79 cb474497 2022-04-19 o return Alias{}, fmt.Errorf("put %s: %w", a.Recipient, err)
84 cb474497 2022-04-19 o // Put creates or updates the given alias in db.
85 cb474497 2022-04-19 o func (db *AliasDB) Put(a Alias) error {
86 cb474497 2022-04-19 o _, err := db.Lookup(a.Recipient)
87 cb474497 2022-04-19 o if err != nil && !errors.Is(err, errRecipientNotExist) {
88 cb474497 2022-04-19 o return fmt.Errorf("lookup %s: %w", a.Recipient, err)
91 cb474497 2022-04-19 o if errors.Is(err, errRecipientNotExist) {
92 cb474497 2022-04-19 o q = "INSERT INTO aliases (recipient, destination, expiry, note) VALUES (?, ?, ?, ?)"
93 71bdede3 2022-04-21 o _, err = db.Exec(q, a.Recipient, a.Destination, a.Expiry.Unix(), a.Note)
94 cb474497 2022-04-19 o } else if err == nil {
95 71bdede3 2022-04-21 o q = "UPDATE aliases SET recipient = ?, destination = ?, expiry = ?, note = ? WHERE recipient = ?"
96 71bdede3 2022-04-21 o _, err = db.Exec(q, a.Recipient, a.Destination, a.Expiry.Unix(), a.Note, a.Recipient)
101 cb474497 2022-04-19 o // Lookup returns the Alias with the recipient rcpt. If no alias exists, an error is returned.
102 cb474497 2022-04-19 o func (db *AliasDB) Lookup(rcpt string) (Alias, error) {
104 cb474497 2022-04-19 o q := "SELECT recipient, destination, expiry, note FROM ALIASES WHERE recipient = ?"
105 71bdede3 2022-04-21 o var expiry int64
106 71bdede3 2022-04-21 o err := db.QueryRow(q, rcpt).Scan(&a.Recipient, &a.Destination, &expiry, &a.Note)
107 cb474497 2022-04-19 o if errors.Is(err, sql.ErrNoRows) {
108 cb474497 2022-04-19 o return Alias{}, errRecipientNotExist
109 cb474497 2022-04-19 o } else if err != nil {
110 cb474497 2022-04-19 o return Alias{}, err
112 71bdede3 2022-04-21 o a.Expiry = time.Unix(expiry, 0)
116 cb474497 2022-04-19 o // Aliases returns all aliases who have their destination address as dest.
117 925168e0 2022-04-14 o func (db *AliasDB) Aliases(dest string) ([]Alias, error) {
118 925168e0 2022-04-14 o rows, err := db.Query("SELECT recipient, destination, expiry, note FROM aliases WHERE destination = ?", dest)
119 925168e0 2022-04-14 o if err != nil {
120 cb474497 2022-04-19 o return nil, err
122 925168e0 2022-04-14 o defer rows.Close()
123 925168e0 2022-04-14 o var aliases []Alias
124 925168e0 2022-04-14 o for rows.Next() {
127 cb474497 2022-04-19 o if err := rows.Scan(&a.Recipient, &a.Destination, &sec, &a.Note); err != nil {
128 925168e0 2022-04-14 o return aliases, err
130 cb474497 2022-04-19 o a.Expiry = time.Unix(sec, 0)
131 925168e0 2022-04-14 o aliases = append(aliases, a)
133 925168e0 2022-04-14 o return aliases, rows.Err()
136 cb474497 2022-04-19 o // Delete deletes any alias with recipient rcpt.
137 cb474497 2022-04-19 o // No error is returned if no alias exists with the given recipient.
138 925168e0 2022-04-14 o func (db *AliasDB) Delete(rcpt string) error {
139 925168e0 2022-04-14 o _, err := db.Exec("DELETE FROM aliases WHERE recipient = ?", rcpt)
140 925168e0 2022-04-14 o if err != nil {
141 925168e0 2022-04-14 o return fmt.Errorf("delete %s: %w", rcpt, err)