1 // package aliases provides encoding and decoding of Unix alias(5) files
2 // read by systems like Postfix and OpenBSD's smtpd(8).
14 type Aliases map[string]string
20 func NewEncoder(w io.Writer) *Encoder {
21 return &Encoder{bufio.NewWriter(w)}
24 func (e *Encoder) Encode(m Aliases) error {
26 for rcpt, dest := range m {
27 _, err := e.w.WriteString(fmt.Sprintf("%s: %s\n", rcpt, dest))
29 return fmt.Errorf("write alias %s: %w", rcpt, err)
35 func Parse(r io.Reader) (Aliases, error) {
37 sc := bufio.NewScanner(r)
39 line := strings.TrimSpace(sc.Text())
40 if strings.HasPrefix(line, "#") {
41 continue // skip comments
44 continue // skip blank lines
47 rcpt, dest, err := parseLine(line)
49 return nil, fmt.Errorf("parse: %w", err)
54 return nil, fmt.Errorf("parse: %w", sc.Err())
59 // Load reads and returns named aliases file.
60 func Load(name string) (Aliases, error) {
61 f, err := os.Open(name)
63 return nil, fmt.Errorf("load: %w", err)
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)
74 return fmt.Errorf("put: %w", err)
77 if err := NewEncoder(f).Encode(m); err != nil {
78 return fmt.Errorf("put: %w", err)
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)
88 return "", "", fmt.Errorf("parse line: too many fields")
89 } else if len(a) < 2 {
90 return "", "", fmt.Errorf("parse line: too few fields")
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)])
97 if strings.Contains(recipient, ":") {
98 return "", "", errors.New("parse line: recipient contains colon character")
101 return "", "", errors.New("parse line: empty recipient")
103 if destination == "" {
104 return "", "", errors.New("parse line: empty destination")
106 return recipient, destination, nil