1
0
mirror of https://github.com/jhillyerd/inbucket.git synced 2025-12-17 17:47:03 +00:00

Add ability to name mailboxes after domain of email (#140)

* Add ability to name mailboxes after domain of email
* Update argument documentation for 'domain' filtering
* Update address policy to verify domain against RFC and return error on invalid domain
This commit is contained in:
Matt John
2019-08-10 22:13:21 +01:00
committed by James Hillyerd
parent b6a6cc6708
commit c78656b400
4 changed files with 148 additions and 67 deletions

View File

@@ -76,8 +76,14 @@ Prior to the addition of the mailbox naming setting, Inbucket always operated in
local mode. Regardless of this setting, the `+` wildcard/extension is not local mode. Regardless of this setting, the `+` wildcard/extension is not
incorporated into the mailbox name. incorporated into the mailbox name.
#### `domain` ensures the local-part is removed, such that:
- `james@inbucket.org` is stored in `inbucket.org`
- `matt@inbucket.org` is stored in `inbucket.org`
- `matt@noinbucket.com` is stored in `notinbucket.com`
- Default: `local` - Default: `local`
- Values: one of `local` or `full` - Values: one of `local` or `full` or `domain`
## SMTP ## SMTP

View File

@@ -38,6 +38,7 @@ const (
UnknownNaming mbNaming = iota UnknownNaming mbNaming = iota
LocalNaming LocalNaming
FullNaming FullNaming
DomainNaming
) )
// Decode a naming strategy from string. // Decode a naming strategy from string.
@@ -47,6 +48,8 @@ func (n *mbNaming) Decode(v string) error {
*n = LocalNaming *n = LocalNaming
case "full": case "full":
*n = FullNaming *n = FullNaming
case "domain":
*n = DomainNaming
default: default:
return fmt.Errorf("Unknown MailboxNaming strategy: %q", v) return fmt.Errorf("Unknown MailboxNaming strategy: %q", v)
} }
@@ -56,7 +59,7 @@ func (n *mbNaming) Decode(v string) error {
// Root contains global configuration, and structs with for specific sub-systems. // Root contains global configuration, and structs with for specific sub-systems.
type Root struct { type Root struct {
LogLevel string `required:"true" default:"info" desc:"debug, info, warn, or error"` LogLevel string `required:"true" default:"info" desc:"debug, info, warn, or error"`
MailboxNaming mbNaming `required:"true" default:"local" desc:"Use local or full addressing"` MailboxNaming mbNaming `required:"true" default:"local" desc:"Use local, full or domain addressing"`
SMTP SMTP SMTP SMTP
POP3 POP3 POP3 POP3
Web Web Web Web

View File

@@ -28,6 +28,20 @@ func (a *Addressing) ExtractMailbox(address string) (string, error) {
if a.Config.MailboxNaming == config.LocalNaming { if a.Config.MailboxNaming == config.LocalNaming {
return local, nil return local, nil
} }
if a.Config.MailboxNaming == config.DomainNaming {
// If no domain is specified, assume this is being
// used for mailbox lookup via the API.
if domain == "" {
if ValidateDomainPart(local) == false {
return "", fmt.Errorf("Domain part %q in %q failed validation", local, address)
}
return local, nil
}
if ValidateDomainPart(domain) == false {
return "", fmt.Errorf("Domain part %q in %q failed validation", domain, address)
}
return domain, nil
}
if a.Config.MailboxNaming != config.FullNaming { if a.Config.MailboxNaming != config.FullNaming {
return "", fmt.Errorf("Unknown MailboxNaming value: %v", a.Config.MailboxNaming) return "", fmt.Errorf("Unknown MailboxNaming value: %v", a.Config.MailboxNaming)
} }
@@ -128,8 +142,8 @@ func ValidateDomainPart(domain string) bool {
hasAlphaNum = true hasAlphaNum = true
labelLen++ labelLen++
case c == '-': case c == '-':
if prev == '.' { if prev == '.' || prev == '-' {
// Cannot lead with hyphen. // Cannot lead with hyphen or double hyphen.
return false return false
} }
case c == '.': case c == '.':

View File

@@ -125,111 +125,139 @@ func TestShouldStoreDomain(t *testing.T) {
func TestExtractMailboxValid(t *testing.T) { func TestExtractMailboxValid(t *testing.T) {
localPolicy := policy.Addressing{Config: &config.Root{MailboxNaming: config.LocalNaming}} localPolicy := policy.Addressing{Config: &config.Root{MailboxNaming: config.LocalNaming}}
fullPolicy := policy.Addressing{Config: &config.Root{MailboxNaming: config.FullNaming}} fullPolicy := policy.Addressing{Config: &config.Root{MailboxNaming: config.FullNaming}}
domainPolicy := policy.Addressing{Config: &config.Root{MailboxNaming: config.DomainNaming}}
testTable := []struct { testTable := []struct {
input string // Input to test input string // Input to test
local string // Expected output when mailbox naming = local local string // Expected output when mailbox naming = local
full string // Expected output when mailbox naming = full full string // Expected output when mailbox naming = full
domain string // Expected output when mailbox naming = domain
}{ }{
{ {
input: "mailbox", input: "mailbox",
local: "mailbox", local: "mailbox",
full: "mailbox", full: "mailbox",
domain: "mailbox",
}, },
{ {
input: "user123", input: "user123",
local: "user123", local: "user123",
full: "user123", full: "user123",
domain: "user123",
}, },
{ {
input: "MailBOX", input: "MailBOX",
local: "mailbox", local: "mailbox",
full: "mailbox", full: "mailbox",
domain: "mailbox",
}, },
{ {
input: "First.Last", input: "First.Last",
local: "first.last", local: "first.last",
full: "first.last", full: "first.last",
domain: "first.last",
}, },
{ {
input: "user+label", input: "user+label",
local: "user", local: "user",
full: "user", full: "user",
domain: "user",
}, },
{ {
input: "chars!#$%", input: "chars!#$%",
local: "chars!#$%", local: "chars!#$%",
full: "chars!#$%", full: "chars!#$%",
domain: "",
}, },
{ {
input: "chars&'*-", input: "chars&'*-",
local: "chars&'*-", local: "chars&'*-",
full: "chars&'*-", full: "chars&'*-",
domain: "",
}, },
{ {
input: "chars=/?^", input: "chars=/?^",
local: "chars=/?^", local: "chars=/?^",
full: "chars=/?^", full: "chars=/?^",
domain: "",
}, },
{ {
input: "chars_`.{", input: "chars_`.{",
local: "chars_`.{", local: "chars_`.{",
full: "chars_`.{", full: "chars_`.{",
domain: "",
}, },
{ {
input: "chars|}~", input: "chars|}~",
local: "chars|}~", local: "chars|}~",
full: "chars|}~", full: "chars|}~",
domain: "",
}, },
{ {
input: "mailbox@domain.com", input: "mailbox@domain.com",
local: "mailbox", local: "mailbox",
full: "mailbox@domain.com", full: "mailbox@domain.com",
domain: "domain.com",
}, },
{ {
input: "user123@domain.com", input: "user123@domain.com",
local: "user123", local: "user123",
full: "user123@domain.com", full: "user123@domain.com",
domain: "domain.com",
}, },
{ {
input: "MailBOX@domain.com", input: "MailBOX@domain.com",
local: "mailbox", local: "mailbox",
full: "mailbox@domain.com", full: "mailbox@domain.com",
domain: "domain.com",
}, },
{ {
input: "First.Last@domain.com", input: "First.Last@domain.com",
local: "first.last", local: "first.last",
full: "first.last@domain.com", full: "first.last@domain.com",
domain: "domain.com",
}, },
{ {
input: "user+label@domain.com", input: "user+label@domain.com",
local: "user", local: "user",
full: "user@domain.com", full: "user@domain.com",
domain: "domain.com",
}, },
{ {
input: "chars!#$%@domain.com", input: "chars!#$%@domain.com",
local: "chars!#$%", local: "chars!#$%",
full: "chars!#$%@domain.com", full: "chars!#$%@domain.com",
domain: "domain.com",
}, },
{ {
input: "chars&'*-@domain.com", input: "chars&'*-@domain.com",
local: "chars&'*-", local: "chars&'*-",
full: "chars&'*-@domain.com", full: "chars&'*-@domain.com",
domain: "domain.com",
}, },
{ {
input: "chars=/?^@domain.com", input: "chars=/?^@domain.com",
local: "chars=/?^", local: "chars=/?^",
full: "chars=/?^@domain.com", full: "chars=/?^@domain.com",
domain: "domain.com",
}, },
{ {
input: "chars_`.{@domain.com", input: "chars_`.{@domain.com",
local: "chars_`.{", local: "chars_`.{",
full: "chars_`.{@domain.com", full: "chars_`.{@domain.com",
domain: "domain.com",
}, },
{ {
input: "chars|}~@domain.com", input: "chars|}~@domain.com",
local: "chars|}~", local: "chars|}~",
full: "chars|}~@domain.com", full: "chars|}~@domain.com",
domain: "domain.com",
},
{
input: "chars|}~@example.co.uk",
local: "chars|}~",
full: "chars|}~@example.co.uk",
domain: "example.co.uk",
}, },
} }
for _, tc := range testTable { for _, tc := range testTable {
@@ -247,12 +275,20 @@ func TestExtractMailboxValid(t *testing.T) {
t.Errorf("Parsing %q, expected %q, got %q", tc.input, tc.full, result) t.Errorf("Parsing %q, expected %q, got %q", tc.input, tc.full, result)
} }
} }
if result, err := domainPolicy.ExtractMailbox(tc.input); tc.domain != "" && err != nil {
t.Errorf("Error while parsing with domain naming %q: %v", tc.input, err)
} else {
if result != tc.domain {
t.Errorf("Parsing %q, expected %q, got %q", tc.input, tc.domain, result)
}
}
} }
} }
func TestExtractMailboxInvalid(t *testing.T) { func TestExtractMailboxInvalid(t *testing.T) {
localPolicy := policy.Addressing{Config: &config.Root{MailboxNaming: config.LocalNaming}} localPolicy := policy.Addressing{Config: &config.Root{MailboxNaming: config.LocalNaming}}
fullPolicy := policy.Addressing{Config: &config.Root{MailboxNaming: config.FullNaming}} fullPolicy := policy.Addressing{Config: &config.Root{MailboxNaming: config.FullNaming}}
domainPolicy := policy.Addressing{Config: &config.Root{MailboxNaming: config.DomainNaming}}
// Test local mailbox naming policy. // Test local mailbox naming policy.
localInvalidTable := []struct { localInvalidTable := []struct {
input, msg string input, msg string
@@ -282,6 +318,28 @@ func TestExtractMailboxInvalid(t *testing.T) {
t.Errorf("Didn't get an error while parsing in full mode %q: %v", tt.input, tt.msg) t.Errorf("Didn't get an error while parsing in full mode %q: %v", tt.input, tt.msg)
} }
} }
// Test domain mailbox naming policy.
domainInvalidTable := []struct {
input, msg string
}{
{"", "Empty mailbox name is not permitted"},
{"user@host@domain.com", "@ symbol not permitted"},
{"first.last@dom ain.com", "Space not permitted"},
{"first\"last@domain.com", "Double quote not permitted"},
{"first\nlast@domain.com", "Control chars not permitted"},
{"first.last@chars!#$%.com", "Invalid domain name"},
{"first.last@.example.com", "Domain cannot start with dot"},
{"first.last@-example.com", "Domain canont start with dash"},
{"first.last@example.com-", "Domain cannot end with dash"},
{"first.last@example..com", "Domain cannot contain double dots"},
{"first.last@example--com", "Domain cannot contain double dashes"},
{"first.last@example.-com", "Domain cannot contain concecutive symbols"},
}
for _, tt := range domainInvalidTable {
if _, err := domainPolicy.ExtractMailbox(tt.input); err == nil {
t.Errorf("Didn't get an error while parsing in domain mode %q: %v", tt.input, tt.msg)
}
}
} }
func TestValidateDomain(t *testing.T) { func TestValidateDomain(t *testing.T) {