diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 0000000..d8e9561 --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 8a2c9e5..3ce3588 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,9 +1,5 @@ - - diff --git a/postfix-config/comma-list-scanner/comma-list-scanner.go b/postfix-config/comma-list-scanner/comma-list-scanner.go index 461eb99..6a3e6a1 100644 --- a/postfix-config/comma-list-scanner/comma-list-scanner.go +++ b/postfix-config/comma-list-scanner/comma-list-scanner.go @@ -3,6 +3,7 @@ package comma_list_scanner import ( "bufio" "bytes" + "fmt" "io" ) @@ -18,8 +19,15 @@ func NewCommaListScanner(r io.Reader) *CommaListScanner { if atEOF && len(data) == 0 { return 0, nil, nil } - if i := bytes.IndexByte(data, ','); i >= 0 { - return i + 1, bytes.TrimSpace(data[0:i]), nil + println("data", fmt.Sprintf("%s", data)) + println("index", bytes.IndexAny(data, " ,")) + if i := bytes.IndexAny(data, " ,"); i >= 0 { + // consume all spaces after the comma + j := i + 1 + for j < len(data) && data[j] == ' ' { + j++ + } + return j, bytes.TrimSpace(data[0:i]), nil } // If we're at EOF, we have a final non-terminated line. Return it. if atEOF { @@ -34,6 +42,7 @@ func NewCommaListScanner(r io.Reader) *CommaListScanner { func (c *CommaListScanner) Scan() bool { if c.r.Scan() { c.text = c.r.Text() + return true } c.err = c.r.Err() return false @@ -42,3 +51,7 @@ func (c *CommaListScanner) Scan() bool { func (c *CommaListScanner) Text() string { return c.text } + +func (c *CommaListScanner) Err() error { + return c.err +} diff --git a/postfix-config/comma-list-scanner/comma-list-scanner_test.go b/postfix-config/comma-list-scanner/comma-list-scanner_test.go index 7e8fa3b..a4b779d 100644 --- a/postfix-config/comma-list-scanner/comma-list-scanner_test.go +++ b/postfix-config/comma-list-scanner/comma-list-scanner_test.go @@ -6,22 +6,40 @@ import ( "testing" ) -var testCommaList = []struct { - text string - out []string -}{ - {"hello, wow this is cool, amazing", []string{"hello", "wow this is cool", "amazing"}}, - {"hello, wow this is cool, amazing", []string{"hello", "wow this is cool", "amazing"}}, +var testCommaList = []string{ + "hello, wow-this-is-cool, amazing", + "hello, wow-this-is-cool", + "hello, wow-this-is-cool, ", + "hello, wow-this-is-cool,", + ",hello, wow-this-is-cool", + ",hello, wow-this-is-cool,", + "hello, wow-this-is-cool,,,", } func TestNewCommaListScanner(t *testing.T) { for _, i := range testCommaList { - t.Run(i.text, func(t *testing.T) { - s := NewCommaListScanner(strings.NewReader(i.text)) - n := 0 + t.Run(i, func(t *testing.T) { + // use comma list scanner + s := NewCommaListScanner(strings.NewReader(i)) + n := strings.Count(i, ",") + a := make([]string, 0, n+1) for s.Scan() { - assert.Equal(t, i.out[n], s.Text()) + a = append(a, s.Text()) } + assert.NoError(t, s.Err()) + + // test against splitting and trimming strings + b := strings.Split(i, ",") + for i := 0; i < len(b); i++ { + c := strings.TrimSpace(b[i]) + if c == "" { + b = append(b[0:i], b[i+1:]...) + i-- + } else { + b[i] = c + } + } + assert.Equal(t, b, a) }) } } diff --git a/postfix-config/config-parser/config-parser.go b/postfix-config/config-parser/config-parser.go index f349ea2..7738bd3 100644 --- a/postfix-config/config-parser/config-parser.go +++ b/postfix-config/config-parser/config-parser.go @@ -38,7 +38,7 @@ scanAgain: } func (c *ConfigParser) Pair() (string, string) { - return c.pair[0], c.pair[1] + return strings.TrimSpace(c.pair[0]), strings.TrimSpace(c.pair[1]) } func (c *ConfigParser) Err() error { diff --git a/postfix-config/config.go b/postfix-config/config.go index e36c104..4057930 100644 --- a/postfix-config/config.go +++ b/postfix-config/config.go @@ -9,7 +9,20 @@ type Config struct { VirtualMailboxMaps mapProvider.MapProvider AliasMaps mapProvider.MapProvider LocalRecipientMaps mapProvider.MapProvider - SmtpdSenderLoginMaps string // TODO(melon): union map? + SmtpdSenderLoginMaps mapProvider.MapProvider +} + +var parseProviderData = map[string]string{ + "virtual_mailbox_domains": "comma", + "virtual_alias_maps": "comma", + "virtual_mailbox_maps": "comma", + "alias_maps": "comma", + "local_recipient_maps": "comma", + "smtpd_sender_login_maps": "union", +} + +func (c *Config) ParseProvider(k string) string { + return parseProviderData[k] } func (c *Config) SetKey(k string, m mapProvider.MapProvider) { @@ -25,14 +38,6 @@ func (c *Config) SetKey(k string, m mapProvider.MapProvider) { case "local_recipient_maps": c.LocalRecipientMaps = m case "smtpd_sender_login_maps": - c.SmtpdSenderLoginMaps = "" + c.SmtpdSenderLoginMaps = m } } - -func (c *Config) NeedsMapProvider(k string) bool { - switch k { - case "virtual_mailbox_domains", "virtual_alias_maps", "virtual_mailbox_maps", "alias_maps", "local_recipient_maps", "smtpd_sender_login_maps": - return true - } - return false -} diff --git a/postfix-config/decoder.go b/postfix-config/decoder.go index 4dfd143..0d96848 100644 --- a/postfix-config/decoder.go +++ b/postfix-config/decoder.go @@ -1,18 +1,20 @@ package postfix_config import ( - "bufio" + "errors" "fmt" + commaListScanner "github.com/1f349/lotus/postfix-config/comma-list-scanner" configParser "github.com/1f349/lotus/postfix-config/config-parser" mapProvider "github.com/1f349/lotus/postfix-config/map-provider" "io" + "path/filepath" "strings" ) type Decoder struct { - r *configParser.ConfigParser - v *Config - t map[string]string + r *configParser.ConfigParser + temp map[string]string + basePath string } func NewDecoder(r io.Reader) *Decoder { @@ -20,30 +22,87 @@ func NewDecoder(r io.Reader) *Decoder { } func (d *Decoder) Load() error { - d.v = &Config{} for d.r.Scan() { k, v := d.r.Pair() - if d.v.NeedsMapProvider(k) { + d.temp[k] = v + } + if err := d.r.Err(); err != nil { + return err + } + + switch d.value.ParseProvider(k) { + case "comma": m := mapProvider.SequenceMapProvider{} - s := bufio.NewScanner(strings.NewReader(v)) - s.Split(bufio.ScanWords) + s := commaListScanner.NewCommaListScanner(strings.NewReader(v)) for s.Scan() { a := s.Text() println("a", a) if strings.HasPrefix(a, "$") { - // is variable + m = append(m, &mapProvider.Variable{Name: a[1:]}) + continue } - n := strings.IndexByte(a, ':') - if n == -1 { - return fmt.Errorf("missing prefix") + + v2, err := d.createValue(a) + if err != nil { + return err } + m = append(m, v2) } if err := s.Err(); err != nil { return err } - d.v.SetKey(k, m) + d.value.SetKey(k, m) + case "union": + if !strings.HasPrefix(v, "unionmap:{") || !strings.HasSuffix(v, "}") { + return errors.New("key requires a union map") + } + v = v[len("unionmap:{") : len(v)-1] + + m := mapProvider.SequenceMapProvider{} + s := commaListScanner.NewCommaListScanner(strings.NewReader(v)) + for s.Scan() { + a := s.Text() + v2, err := d.createValue(a) + if err != nil { + return err + } + m = append(m, v2) + } + default: + return fmt.Errorf("key '%s' has no defined parse provider", k) } } return d.r.Err() } + +func (d *Decoder) createValue(a string) (mapProvider.MapProvider, error) { + n := strings.IndexByte(a, ':') + if n == -1 { + return nil, fmt.Errorf("missing prefix") + } + + namespace := a[:n] + value := a[n+1:] + switch namespace { + case "mysql": + if !filepath.IsAbs(value) { + value = filepath.Join(d.basePath, value) + } + provider, err := mapProvider.NewMySqlMapProvider(value) + if err != nil { + return nil, err + } + return provider, nil + case "hash": + if !filepath.IsAbs(value) { + value = filepath.Join(d.basePath, value) + } + provider, err := mapProvider.NewHashMapProvider(value) + if err != nil { + return nil, err + } + return provider, nil + } + return nil, errors.New("invalid provider namespace") +} diff --git a/postfix-config/decoder_test.go b/postfix-config/decoder_test.go index e646731..7bc4e2e 100644 --- a/postfix-config/decoder_test.go +++ b/postfix-config/decoder_test.go @@ -5,6 +5,8 @@ import ( _ "embed" configParser "github.com/1f349/lotus/postfix-config/config-parser" "github.com/stretchr/testify/assert" + "os" + "path/filepath" "testing" ) @@ -12,7 +14,15 @@ import ( var exampleConfig []byte func TestDecoder_Load(t *testing.T) { + // get working directory + wd, err := os.Getwd() + assert.NoError(t, err) + + // read example config b := bytes.NewReader(exampleConfig) - d := &Decoder{r: configParser.NewConfigParser(b)} + d := &Decoder{ + r: configParser.NewConfigParser(b), + basePath: filepath.Join(wd, "test-data"), + } assert.NoError(t, d.Load()) } diff --git a/postfix-config/example.cf b/postfix-config/example.cf index c16fd44..359b837 100644 --- a/postfix-config/example.cf +++ b/postfix-config/example.cf @@ -1,10 +1,10 @@ # this only contains the relevant config properties -recipient_delimiter = + +#recipient_delimiter = + -virtual_mailbox_domains = mysql:/etc/postfix/sql/mysql_virtual_domains_maps.cf -virtual_alias_maps = mysql:/etc/postfix/sql/mysql_virtual_alias_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_wildcard_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_domain_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_user_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_userdomain_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_domain_catchall_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_user_catchall_maps.cf -virtual_mailbox_maps = mysql:/etc/postfix/sql/mysql_virtual_mailbox_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_domain_mailbox_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_user_mailbox_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_userdomain_mailbox_maps.cf -alias_maps = hash:/etc/aliases $virtual_alias_maps +virtual_mailbox_domains = mysql:mysql_virtual_domains_maps.cf +virtual_alias_maps = mysql:mysql_virtual_alias_maps.cf, mysql:mysql_virtual_alias_wildcard_maps.cf, mysql:mysql_virtual_alias_domain_maps.cf, mysql:mysql_virtual_alias_user_maps.cf, mysql:mysql_virtual_alias_userdomain_maps.cf, mysql:mysql_virtual_alias_domain_catchall_maps.cf, mysql:mysql_virtual_alias_user_catchall_maps.cf +virtual_mailbox_maps = mysql:mysql_virtual_mailbox_maps.cf, mysql:mysql_virtual_alias_domain_mailbox_maps.cf, mysql:mysql_virtual_alias_user_mailbox_maps.cf, mysql:mysql_virtual_alias_userdomain_mailbox_maps.cf +alias_maps = hash:aliases.txt $virtual_alias_maps local_recipient_maps = $virtual_mailbox_maps $alias_maps -smtpd_sender_login_maps = unionmap:{ hash:/etc/aliases, mysql:/etc/postfix/sql/mysql_sender_alias_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_domain_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_user_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_userdomain_maps.cf } +smtpd_sender_login_maps = unionmap:{ hash:aliases.txt, mysql:mysql_sender_alias_maps.cf, mysql:mysql_virtual_alias_maps.cf, mysql:mysql_virtual_alias_domain_maps.cf, mysql:mysql_virtual_alias_user_maps.cf, mysql:mysql_virtual_alias_userdomain_maps.cf } diff --git a/postfix-config/test-data/aliases.txt b/postfix-config/test-data/aliases.txt new file mode 100644 index 0000000..01c5c6a --- /dev/null +++ b/postfix-config/test-data/aliases.txt @@ -0,0 +1 @@ +a: a b \ No newline at end of file diff --git a/postfix-config/test-data/mysql_sender_alias_maps.cf b/postfix-config/test-data/mysql_sender_alias_maps.cf new file mode 100644 index 0000000..e69de29 diff --git a/postfix-config/test-data/mysql_virtual_alias_domain_catchall_maps.cf b/postfix-config/test-data/mysql_virtual_alias_domain_catchall_maps.cf new file mode 100644 index 0000000..e69de29 diff --git a/postfix-config/test-data/mysql_virtual_alias_domain_mailbox_maps.cf b/postfix-config/test-data/mysql_virtual_alias_domain_mailbox_maps.cf new file mode 100644 index 0000000..e69de29 diff --git a/postfix-config/test-data/mysql_virtual_alias_domain_maps.cf b/postfix-config/test-data/mysql_virtual_alias_domain_maps.cf new file mode 100644 index 0000000..e69de29 diff --git a/postfix-config/test-data/mysql_virtual_alias_maps.cf b/postfix-config/test-data/mysql_virtual_alias_maps.cf new file mode 100644 index 0000000..e69de29 diff --git a/postfix-config/test-data/mysql_virtual_alias_user_catchall_maps.cf b/postfix-config/test-data/mysql_virtual_alias_user_catchall_maps.cf new file mode 100644 index 0000000..e69de29 diff --git a/postfix-config/test-data/mysql_virtual_alias_user_mailbox_maps.cf b/postfix-config/test-data/mysql_virtual_alias_user_mailbox_maps.cf new file mode 100644 index 0000000..e69de29 diff --git a/postfix-config/test-data/mysql_virtual_alias_user_maps.cf b/postfix-config/test-data/mysql_virtual_alias_user_maps.cf new file mode 100644 index 0000000..e69de29 diff --git a/postfix-config/test-data/mysql_virtual_alias_userdomain_mailbox_maps.cf b/postfix-config/test-data/mysql_virtual_alias_userdomain_mailbox_maps.cf new file mode 100644 index 0000000..e69de29 diff --git a/postfix-config/test-data/mysql_virtual_alias_userdomain_maps.cf b/postfix-config/test-data/mysql_virtual_alias_userdomain_maps.cf new file mode 100644 index 0000000..e69de29 diff --git a/postfix-config/test-data/mysql_virtual_alias_wildcard_maps.cf b/postfix-config/test-data/mysql_virtual_alias_wildcard_maps.cf new file mode 100644 index 0000000..e69de29 diff --git a/postfix-config/test-data/mysql_virtual_domains_maps.cf b/postfix-config/test-data/mysql_virtual_domains_maps.cf new file mode 100644 index 0000000..e69de29 diff --git a/postfix-config/test-data/mysql_virtual_mailbox_maps.cf b/postfix-config/test-data/mysql_virtual_mailbox_maps.cf new file mode 100644 index 0000000..e69de29