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