Blob


1 // package aliases provides encoding and decoding of Unix alias(5) files
2 // read by systems like Postfix and OpenBSD's smtpd(8).
3 package aliases
5 import (
6 "bufio"
7 "errors"
8 "fmt"
9 "io"
10 "os"
11 "strings"
12 )
14 type Aliases map[string]string
16 type Encoder struct {
17 w *bufio.Writer
18 }
20 func NewEncoder(w io.Writer) *Encoder {
21 return &Encoder{bufio.NewWriter(w)}
22 }
24 func (e *Encoder) Encode(m Aliases) error {
25 defer e.w.Flush()
26 for rcpt, dest := range m {
27 _, err := e.w.WriteString(fmt.Sprintf("%s: %s\n", rcpt, dest))
28 if err != nil {
29 return fmt.Errorf("write alias %s: %w", rcpt, err)
30 }
31 }
32 return nil
33 }
35 func Parse(r io.Reader) (Aliases, error) {
36 m := make(Aliases)
37 sc := bufio.NewScanner(r)
38 for sc.Scan() {
39 line := strings.TrimSpace(sc.Text())
40 if strings.HasPrefix(line, "#") {
41 continue // skip comments
42 }
43 if line == "" {
44 continue // skip blank lines
45 }
47 rcpt, dest, err := parseLine(line)
48 if err != nil {
49 return nil, fmt.Errorf("parse: %w", err)
50 }
51 m[rcpt] = dest
52 }
53 if sc.Err() != nil {
54 return nil, fmt.Errorf("parse: %w", sc.Err())
55 }
56 return m, nil
57 }
59 // Load reads and returns named aliases file.
60 func Load(name string) (Aliases, error) {
61 f, err := os.Open(name)
62 if err != nil {
63 return nil, fmt.Errorf("load: %w", err)
64 }
65 defer f.Close()
66 return Parse(f)
67 }
69 // Put writes aliases to the named file.
70 // The file is truncated if it already exists, otherwise it is created.
71 func Put(m Aliases, name string) error {
72 f, err := os.Create(name)
73 if err != nil {
74 return fmt.Errorf("put: %w", err)
75 }
76 defer f.Close()
77 if err := NewEncoder(f).Encode(m); err != nil {
78 return fmt.Errorf("put: %w", err)
79 }
80 return nil
81 }
83 // parseLine parses a line of the form "foo: bar@example.com", returning two fields:
84 // the recipient (foo) and destination (bar@example.com).
85 func parseLine(s string) (recipient, destination string, err error) {
86 a := strings.Fields(s)
87 if len(a) > 2 {
88 return "", "", fmt.Errorf("parse line: too many fields")
89 } else if len(a) < 2 {
90 return "", "", fmt.Errorf("parse line: too few fields")
91 }
93 recipient, destination = strings.TrimSuffix(a[0], ":"), a[1]
94 if !strings.HasSuffix(a[0], ":") {
95 return "", "", fmt.Errorf("parse line: invalid recipient: expected %q, got %q", ":", recipient[len(recipient)])
96 }
97 if strings.Contains(recipient, ":") {
98 return "", "", errors.New("parse line: recipient contains colon character")
99 }
100 if recipient == "" {
101 return "", "", errors.New("parse line: empty recipient")
103 if destination == "" {
104 return "", "", errors.New("parse line: empty destination")
106 return recipient, destination, nil