diff --git a/ratelimiter.go b/ratelimiter.go new file mode 100644 index 0000000..93d2c45 --- /dev/null +++ b/ratelimiter.go @@ -0,0 +1,67 @@ +package main + +import ( + "context" + "os" + "strings" + "sync" + "time" + + "github.com/maypok86/otter" +) + +var lock = &sync.Mutex{} + +type single struct { + context context.Context + cache otter.Cache[string, string] + r *Remote +} + +var remoteCache map[*Remote]*single = make(map[*Remote]*single) + +func getContext(r *Remote) *single { + + if remoteCache[r] == nil { + lock.Lock() + defer lock.Unlock() + if remoteCache[r] == nil { + remoteCache[r] = &single{} + + remoteCache[r].context = context.Background() + remoteCache[r].r = r + + cache, err := otter.MustBuilder[string, string](10_000). + CollectStats(). + Cost(func(key string, value string) uint32 { + return 1 + }).DeletionListener(func(key, value string, cause otter.DeletionCause) { + log.Infof("Evicted %s %s %v ", key, value, cause) + + parts := strings.Split(value, ";") + if len(parts) < 3 { + log.Info("Should have had at least three parts") + } else { + msg, err := os.ReadFile("/tmp/" + key + ".mail") + if err != nil { + log.Errorf("cannot read file %s", key+".mail") + + } else { + from := parts[1] + to := parts[2:] + SendMail(r, from, to, msg) + os.Remove("/tmp/" + key + ".mail") + } + } + }). + WithTTL(time.Minute). + Build() + + if err != nil { + panic(err) + } + remoteCache[r].cache = cache + } + } + return remoteCache[r] +} diff --git a/smtp.go b/smtp.go index dd44e89..798bbc4 100644 --- a/smtp.go +++ b/smtp.go @@ -18,7 +18,6 @@ package main import ( - "context" "crypto/tls" "encoding/base64" "errors" @@ -29,11 +28,9 @@ import ( "net/textproto" "os" "strings" - "sync" "time" "github.com/chrj/smtpd" - "github.com/maypok86/otter" ) // A Client represents a client connection to an SMTP server. @@ -328,62 +325,6 @@ var testHookStartTLS func(*tls.Config) // nil, except for tests // functionality. Higher-level packages exist outside of the standard // library. -var lock = &sync.Mutex{} - -type single struct { - context context.Context - cache otter.Cache[string, string] - r *Remote -} - -var remoteCache map[*Remote]*single = make(map[*Remote]*single) - -func getContext(r *Remote) *single { - - if remoteCache[r] == nil { - lock.Lock() - defer lock.Unlock() - if remoteCache[r] == nil { - remoteCache[r] = &single{} - - remoteCache[r].context = context.Background() - remoteCache[r].r = r - - cache, err := otter.MustBuilder[string, string](10_000). - CollectStats(). - Cost(func(key string, value string) uint32 { - return 1 - }).DeletionListener(func(key, value string, cause otter.DeletionCause) { - log.Infof("Evicted %s %s %v ", key, value, cause) - - parts := strings.Split(value, ";") - if len(parts) < 3 { - log.Info("Should have had at least three parts") - } else { - msg, err := os.ReadFile("/tmp/" + key + ".mail") - if err != nil { - log.Errorf("cannot read file %s", key+".mail") - - } else { - from := parts[1] - to := parts[2:] - SendMail(r, from, to, msg) - os.Remove("/tmp/" + key + ".mail") - } - } - }). - WithTTL(time.Minute). - Build() - - if err != nil { - panic(err) - } - remoteCache[r].cache = cache - } - } - return remoteCache[r] -} - func SendMail(r *Remote, from string, to []string, msg []byte) error { if r.RateLimiter != nil { // Do the background in the main diff --git a/smtprelay.ini b/smtprelay.ini index d5766cb..ca8826f 100644 --- a/smtprelay.ini +++ b/smtprelay.ini @@ -123,8 +123,12 @@ listen = 127.0.0.1:1025 ;remotes = smtp://user:pass@server:2525/overridden@email.com?auth=login ; Multiple remotes, space delimited -remotes = smtp://127.0.0.1:2525?rate=99/21m -; remotes = smtp://127.0.0.1:2525?rate=1/1m +;remotes = smtp://127.0.0.1:1025 starttls://user:pass@smtp.mailgun.org:587 + +; rate limit +; remotes = smtp://127.0.0.1:2525?rate=99/21m +remotes = smtp://127.0.0.1:2525?rate=1/1m smtp://127.0.0.1:2527?rate=6/1m + ; Pipe messages to external command ;command = /usr/local/bin/script