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

policy: Implement store domain policy for #51

- Update docs, change log, status.html
This commit is contained in:
James Hillyerd
2018-04-01 19:46:44 -07:00
parent a7d2b00a9c
commit 064549f576
9 changed files with 91 additions and 53 deletions

View File

@@ -18,6 +18,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- RedHat `.rpm` package generation to release process. - RedHat `.rpm` package generation to release process.
- Message seen flag in REST and Web UI so you can see which messages have - Message seen flag in REST and Web UI so you can see which messages have
already been read. already been read.
- Recipient domain accept policy; Inbucket can now reject mail to specific
domains.
### Changed ### Changed
- Massive refactor of back-end code. Inbucket should now be both easier and - Massive refactor of back-end code. Inbucket should now be both easier and
@@ -34,9 +36,11 @@ This project adheres to [Semantic Versioning](http://semver.org/).
flag; which is enabled by default for the Docker container. flag; which is enabled by default for the Docker container.
- SMTP and POP3 network tracing is no longer logged regardless of level, but can - SMTP and POP3 network tracing is no longer logged regardless of level, but can
be sent to stdout via `-netdebug` flag. be sent to stdout via `-netdebug` flag.
- Replaced store/nostore config variables with a storage policy that mirrors the
domain accept policy.
### Removed ### Removed
- Support for SIGHUP and log file rotation. - No longer support SIGHUP or log file rotation.
## [v1.3.1] - 2018-03-10 ## [v1.3.1] - 2018-03-10

View File

@@ -11,13 +11,14 @@ variables it supports:
INBUCKET_LOGLEVEL INFO DEBUG, INFO, WARN, or ERROR INBUCKET_LOGLEVEL INFO DEBUG, INFO, WARN, or ERROR
INBUCKET_SMTP_ADDR 0.0.0.0:2500 SMTP server IP4 host:port INBUCKET_SMTP_ADDR 0.0.0.0:2500 SMTP server IP4 host:port
INBUCKET_SMTP_DOMAIN inbucket HELO domain INBUCKET_SMTP_DOMAIN inbucket HELO domain
INBUCKET_SMTP_DOMAINNOSTORE Load testing domain
INBUCKET_SMTP_MAXRECIPIENTS 200 Maximum RCPT TO per message INBUCKET_SMTP_MAXRECIPIENTS 200 Maximum RCPT TO per message
INBUCKET_SMTP_MAXMESSAGEBYTES 10240000 Maximum message size INBUCKET_SMTP_MAXMESSAGEBYTES 10240000 Maximum message size
INBUCKET_SMTP_STOREMESSAGES true Store incoming mail?
INBUCKET_SMTP_DEFAULTACCEPT true Accept all mail by default? INBUCKET_SMTP_DEFAULTACCEPT true Accept all mail by default?
INBUCKET_SMTP_ACCEPTDOMAINS Domains to accept mail for INBUCKET_SMTP_ACCEPTDOMAINS Domains to accept mail for
INBUCKET_SMTP_REJECTDOMAINS Domains to reject mail for INBUCKET_SMTP_REJECTDOMAINS Domains to reject mail for
INBUCKET_SMTP_DEFAULTSTORE true Store all mail by default?
INBUCKET_SMTP_STOREDOMAINS Domains to store mail for
INBUCKET_SMTP_DISCARDDOMAINS Domains to discard mail for
INBUCKET_SMTP_TIMEOUT 300s Idle network timeout INBUCKET_SMTP_TIMEOUT 300s Idle network timeout
INBUCKET_POP3_ADDR 0.0.0.0:1100 POP3 server IP4 host:port INBUCKET_POP3_ADDR 0.0.0.0:1100 POP3 server IP4 host:port
INBUCKET_POP3_DOMAIN inbucket HELLO domain INBUCKET_POP3_DOMAIN inbucket HELLO domain
@@ -78,17 +79,6 @@ Most SMTP clients appear to ignore this value.
- Default: `inbucket` - Default: `inbucket`
### Load Testing/No Store Domain
`INBUCKET_SMTP_DOMAINNOSTORE`
Mail sent to this domain will not be stored by Inbucket. This is helpful if you
are load or soak testing a service, and do not plan to inspect the resulting
emails. Messages sent to a domain other than this will be stored normally.
- Default: None
- Example: `bitbucket.local`
### Maximum Recipients ### Maximum Recipients
`INBUCKET_SMTP_MAXRECIPIENTS` `INBUCKET_SMTP_MAXRECIPIENTS`
@@ -108,17 +98,6 @@ exceeding this size will be rejected during the SMTP `DATA` phase.
- Default: `10240000` (10MB) - Default: `10240000` (10MB)
### Store Messages
`INBUCKET_SMTP_STOREMESSAGES`
This option can be used to disable mail storage entirely. Useful for load
testing, or turning Inbucket into a black hole that will consume our entire
solar system.
- Default: `true`
- Values: `true` or `false`
### Default Recipient Accept Policy ### Default Recipient Accept Policy
`INBUCKET_SMTP_DEFAULTACCEPT` `INBUCKET_SMTP_DEFAULTACCEPT`
@@ -152,6 +131,41 @@ has no effect when false.
- Values: Comma separated list of domains - Values: Comma separated list of domains
- Example: `reject.com,gmail.com` - Example: `reject.com,gmail.com`
### Default Recipient Store Policy
`INBUCKET_SMTP_DEFAULTSTORE`
If true, Inbucket will store mail sent to any domain unless present in the
discard domains list. If false, messages will be discarded unless their domain
is present in the store domains list.
- Default: `true`
- Values: `true` or `false`
### Stored Recipient Domain List
`INBUCKET_SMTP_STOREDOMAINS`
List of domains to store mail for when `INBUCKET_SMTP_DEFAULTSTORE` is false;
has no effect when true.
- Default: None
- Values: Comma separated list of domains
- Example: `localhost,mysite.org`
### Discarded Recipient Domain List
`INBUCKET_SMTP_DISCARDDOMAINS`
Mail sent to these domains will not be stored by Inbucket. This is helpful if
you are load or soak testing a service, and do not plan to inspect the resulting
emails. Messages sent to a domain other than this will be stored normally.
Only has an effect when `INBUCKET_SMTP_DEFAULTSTORE` is true.
- Default: None
- Values: Comma separated list of domains
- Example: `recycle.com,loadtest.org`
### Network Idle Timeout ### Network Idle Timeout
`INBUCKET_SMTP_TIMEOUT` `INBUCKET_SMTP_TIMEOUT`

View File

@@ -3,7 +3,6 @@ package config
import ( import (
"log" "log"
"os" "os"
"strings"
"text/tabwriter" "text/tabwriter"
"time" "time"
@@ -42,13 +41,14 @@ type Root struct {
type SMTP struct { type SMTP struct {
Addr string `required:"true" default:"0.0.0.0:2500" desc:"SMTP server IP4 host:port"` Addr string `required:"true" default:"0.0.0.0:2500" desc:"SMTP server IP4 host:port"`
Domain string `required:"true" default:"inbucket" desc:"HELO domain"` Domain string `required:"true" default:"inbucket" desc:"HELO domain"`
DomainNoStore string `desc:"Load testing domain"`
MaxRecipients int `required:"true" default:"200" desc:"Maximum RCPT TO per message"` MaxRecipients int `required:"true" default:"200" desc:"Maximum RCPT TO per message"`
MaxMessageBytes int `required:"true" default:"10240000" desc:"Maximum message size"` MaxMessageBytes int `required:"true" default:"10240000" desc:"Maximum message size"`
StoreMessages bool `required:"true" default:"true" desc:"Store incoming mail?"`
DefaultAccept bool `required:"true" default:"true" desc:"Accept all mail by default?"` DefaultAccept bool `required:"true" default:"true" desc:"Accept all mail by default?"`
AcceptDomains []string `desc:"Domains to accept mail for"` AcceptDomains []string `desc:"Domains to accept mail for"`
RejectDomains []string `desc:"Domains to reject mail for"` RejectDomains []string `desc:"Domains to reject mail for"`
DefaultStore bool `required:"true" default:"true" desc:"Store all mail by default?"`
StoreDomains []string `desc:"Domains to store mail for"`
DiscardDomains []string `desc:"Domains to discard mail for"`
Timeout time.Duration `required:"true" default:"300s" desc:"Idle network timeout"` Timeout time.Duration `required:"true" default:"300s" desc:"Idle network timeout"`
Debug bool `ignored:"true"` Debug bool `ignored:"true"`
} }
@@ -86,9 +86,10 @@ type Storage struct {
func Process() (*Root, error) { func Process() (*Root, error) {
c := &Root{} c := &Root{}
err := envconfig.Process(prefix, c) err := envconfig.Process(prefix, c)
c.SMTP.DomainNoStore = strings.ToLower(c.SMTP.DomainNoStore)
stringutil.SliceToLower(c.SMTP.AcceptDomains) stringutil.SliceToLower(c.SMTP.AcceptDomains)
stringutil.SliceToLower(c.SMTP.RejectDomains) stringutil.SliceToLower(c.SMTP.RejectDomains)
stringutil.SliceToLower(c.SMTP.StoreDomains)
stringutil.SliceToLower(c.SMTP.DiscardDomains)
return c, err return c, err
} }

View File

@@ -50,10 +50,14 @@ func (a *Addressing) ShouldAcceptDomain(domain string) bool {
return false return false
} }
// ShouldStoreDomain indicates if Inbucket stores email destined for the specified domain. // ShouldStoreDomain indicates if Inbucket stores mail destined for the specified domain.
func (a *Addressing) ShouldStoreDomain(domain string) bool { func (a *Addressing) ShouldStoreDomain(domain string) bool {
if a.Config.StoreMessages { domain = strings.ToLower(domain)
return strings.ToLower(domain) != strings.ToLower(a.Config.DomainNoStore) if a.Config.DefaultStore && !stringutil.SliceContains(a.Config.DiscardDomains, domain) {
return true
}
if !a.Config.DefaultStore && stringutil.SliceContains(a.Config.StoreDomains, domain) {
return true
} }
return false return false
} }

View File

@@ -65,24 +65,24 @@ func TestShouldStoreDomain(t *testing.T) {
// Test with storage enabled. // Test with storage enabled.
ap := &policy.Addressing{ ap := &policy.Addressing{
Config: config.SMTP{ Config: config.SMTP{
DomainNoStore: "Foo.Com", DefaultStore: false,
StoreMessages: true, StoreDomains: []string{"store.com", "a.store.com"},
}, },
} }
testCases := []struct { testCases := []struct {
domain string domain string
want bool want bool
}{ }{
{domain: "bar.com", want: true},
{domain: "foo.com", want: false}, {domain: "foo.com", want: false},
{domain: "FOO.com", want: false}, {domain: "STORE.com", want: true},
{domain: "bar.foo.com", want: true}, {domain: "a.store.com", want: true},
{domain: "b.store.com", want: false},
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.domain, func(t *testing.T) { t.Run(tc.domain, func(t *testing.T) {
got := ap.ShouldStoreDomain(tc.domain) got := ap.ShouldStoreDomain(tc.domain)
if got != tc.want { if got != tc.want {
t.Errorf("Got %v for %q, want: %v", got, tc.domain, tc.want) t.Errorf("Got store %v for %q, want: %v", got, tc.domain, tc.want)
} }
}) })
@@ -90,23 +90,24 @@ func TestShouldStoreDomain(t *testing.T) {
// Test with storage disabled. // Test with storage disabled.
ap = &policy.Addressing{ ap = &policy.Addressing{
Config: config.SMTP{ Config: config.SMTP{
StoreMessages: false, DefaultStore: true,
DiscardDomains: []string{"discard.com", "a.discard.com"},
}, },
} }
testCases = []struct { testCases = []struct {
domain string domain string
want bool want bool
}{ }{
{domain: "bar.com", want: false}, {domain: "foo.com", want: true},
{domain: "foo.com", want: false}, {domain: "DISCARD.com", want: false},
{domain: "FOO.com", want: false}, {domain: "a.discard.com", want: false},
{domain: "bar.foo.com", want: false}, {domain: "b.discard.com", want: true},
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.domain, func(t *testing.T) { t.Run(tc.domain, func(t *testing.T) {
got := ap.ShouldStoreDomain(tc.domain) got := ap.ShouldStoreDomain(tc.domain)
if got != tc.want { if got != tc.want {
t.Errorf("Got %v for %q, want: %v", got, tc.domain, tc.want) t.Errorf("Got store %v for %q, want: %v", got, tc.domain, tc.want)
} }
}) })

View File

@@ -365,10 +365,8 @@ func setupSMTPServer(ds storage.Store) (s *Server, buf *bytes.Buffer, teardown f
cfg := config.SMTP{ cfg := config.SMTP{
Addr: "127.0.0.1:2500", Addr: "127.0.0.1:2500",
Domain: "inbucket.local", Domain: "inbucket.local",
DomainNoStore: "bitbucket.local",
MaxRecipients: 5, MaxRecipients: 5,
MaxMessageBytes: 5000, MaxMessageBytes: 5000,
StoreMessages: true,
DefaultAccept: true, DefaultAccept: true,
RejectDomains: []string{"deny.com"}, RejectDomains: []string{"deny.com"},
Timeout: 5, Timeout: 5,

View File

@@ -97,11 +97,6 @@ func (s *Server) Start(ctx context.Context) {
s.emergencyShutdown() s.emergencyShutdown()
return return
} }
if !s.config.StoreMessages {
slog.Info().Msg("Load test mode active, messages will not be stored")
} else if s.config.DomainNoStore != "" {
slog.Info().Msgf("Messages sent to domain '%v' will be discarded", s.config.DomainNoStore)
}
// Listener go routine. // Listener go routine.
go s.serve(ctx) go s.serve(ctx)
// Wait for shutdown. // Wait for shutdown.

View File

@@ -15,6 +15,7 @@ import (
var TemplateFuncs = template.FuncMap{ var TemplateFuncs = template.FuncMap{
"friendlyTime": FriendlyTime, "friendlyTime": FriendlyTime,
"reverse": Reverse, "reverse": Reverse,
"stringsJoin": strings.Join,
"textToHtml": TextToHTML, "textToHtml": TextToHTML,
} }

View File

@@ -52,10 +52,30 @@ $(document).ready(
<div class="col-sm-8 col-xs-5"><span>{{.webListener}}</span></div> <div class="col-sm-8 col-xs-5"><span>{{.webListener}}</span></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-3 col-xs-7"><b>No-Store Domain:</b></div> <div class="col-sm-3 col-xs-7"><b>Accept Policy:</b></div>
<div class="col-sm-8 col-xs-5"> <div class="col-sm-8 col-xs-5">
{{with .smtpConfig}} {{with .smtpConfig}}
<span>{{or .DomainNoStore .DomainNoStore "Not Configured"}}</span> <span>
{{if .DefaultAccept}}
All domains{{with .RejectDomains}}, except: {{stringsJoin . ", "}}{{end}}
{{else}}
No domains{{with .AcceptDomains}}, except: {{stringsJoin . ", "}}{{end}}
{{end}}
</span>
{{end}}
</div>
</div>
<div class="row">
<div class="col-sm-3 col-xs-7"><b>Store Policy:</b></div>
<div class="col-sm-8 col-xs-5">
{{with .smtpConfig}}
<span>
{{if .DefaultStore}}
All domains{{with .DiscardDomains}}, except: {{stringsJoin . ", "}}{{end}}
{{else}}
No domains{{with .StoreDomains}}, except: {{stringsJoin . ", "}}{{end}}
{{end}}
</span>
{{end}} {{end}}
</div> </div>
</div> </div>