diff --git a/.golangci.yml b/.golangci.yml index f5a179f..12f259c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -4,77 +4,123 @@ run: issues-exit-code: 1 tests: true build-tags: [] - skip-dirs: [] + skip-dirs: + - mocks skip-dirs-use-default: true skip-files: [] modules-download-mode: readonly - allow-parallel-runners: false output: format: colored-line-number print-issued-lines: true print-linter-name: true - uniq-by-line: true - path-prefix: "" sort-results: true linters-settings: + decorder: + dec-order: + - const + - var + - type + - func + dogsled: + max-blank-identifiers: 3 errcheck: + check-type-assertions: true check-blank: true + errchkjson: + report-no-exported: true + exhaustive: + check: + - switch + - map + default-signifies-exhaustive: true gocognit: min-complexity: 15 nestif: - min-complexity: 4 + min-complexity: 5 gocritic: enabled-tags: + - diagnostic + - style - performance + gofmt: + simplify: true + rewrite-rules: + - pattern: 'interface{}' + replacement: 'any' + - pattern: 'a[b:len(a)]' + replacement: 'a[b:]' gofumpt: - lang-version: "1.18" - gosimple: - go: "1.18" - checks: [ "all" ] + extra-rules: true govet: check-shadowing: true - enable: - - atomicalign - - shadow + grouper: + const-require-single-const: true + import-require-single-import: true + var-require-single-var: true misspell: locale: US - staticcheck: - go: "1.18" - checks: [ "all" ] - stylecheck: - go: "1.18" + usestdlibvars: + time-month: true + time-layout: true + crypto-hash: true + default-rpc-path: true + os-dev-null: true + sql-isolation-level: true + tls-signature-scheme: true + constant-kind: true unparam: check-exported: true - unused: - go: "1.18" - gci: - sections: - - standard - - default - - prefix(gitlab.com/etke.cc/postmoogle) - section-separators: - - newLine linters: disable-all: false enable: - - megacheck - - govet + - asasalint + - asciicheck + - bidichk + - bodyclose + - containedctx + - decorder + - dogsled + - dupl + - dupword + - durationcheck - errcheck - - gci + - errchkjson + - errname + - errorlint + - execinquery + - exhaustive + - exportloopref + - forcetypeassert + - gochecknoinits - gocognit - - nestif - # - gocritic # ref: https://github.com/golangci/golangci-lint/issues/2649#issue-1170906525 + - gocritic + - gocyclo + - goerr113 + - gofmt - gofumpt - goimports + - gosec - gosimple + - gosmopolitan - govet + - ineffassign + - makezero + - mirror - misspell + - nestif + - nolintlint + - prealloc + - predeclared + - revive + - sqlclosecheck - staticcheck - - stylecheck + - unconvert - unparam - unused + - usestdlibvars + - wastedassign fast: false diff --git a/bot/access.go b/bot/access.go index f943ae0..87d5fbf 100644 --- a/bot/access.go +++ b/bot/access.go @@ -32,7 +32,7 @@ func (b *Bot) allowUsers(actorID id.UserID) bool { return true } -func (b *Bot) allowAnyone(actorID id.UserID, targetRoomID id.RoomID) bool { +func (b *Bot) allowAnyone(_ id.UserID, _ id.RoomID) bool { return true } @@ -54,7 +54,7 @@ func (b *Bot) allowOwner(actorID id.UserID, targetRoomID id.RoomID) bool { return owner == actorID.String() } -func (b *Bot) allowAdmin(actorID id.UserID, targetRoomID id.RoomID) bool { +func (b *Bot) allowAdmin(actorID id.UserID, _ id.RoomID) bool { return mxidwc.Match(actorID.String(), b.allowedAdmins) } diff --git a/bot/bot.go b/bot/bot.go index 7e03489..77b6a54 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -91,14 +91,14 @@ func New( } // Error message to the log and matrix room -func (b *Bot) Error(ctx context.Context, message string, args ...interface{}) { +func (b *Bot) Error(ctx context.Context, message string, args ...any) { evt := eventFromContext(ctx) threadID := threadIDFromContext(ctx) if threadID == "" { threadID = linkpearl.EventParent(evt.ID, evt.Content.AsMessage()) } - err := fmt.Errorf(message, args...) + err := fmt.Errorf(message, args...) //nolint:goerr113 // we have to b.log.Error().Err(err).Msg(err.Error()) if evt == nil { return diff --git a/bot/command.go b/bot/command.go index 97f8d0b..1261087 100644 --- a/bot/command.go +++ b/bot/command.go @@ -2,6 +2,7 @@ package bot import ( "context" + "errors" "fmt" "strings" "time" @@ -372,7 +373,7 @@ func (b *Bot) handle(ctx context.Context) { if err != nil { b.log.Error().Err(err).Msg("cannot send typing notification") } - defer b.lp.GetClient().UserTyping(evt.RoomID, false, 30*time.Second) //nolint:errcheck + defer b.lp.GetClient().UserTyping(evt.RoomID, false, 30*time.Second) //nolint:errcheck // we don't care if !cmd.allowed(evt.Sender, evt.RoomID) { b.lp.SendNotice(evt.RoomID, "not allowed to do that, kupo") @@ -413,9 +414,9 @@ func (b *Bot) handle(ctx context.Context) { case commandBanlistTotals: b.runBanlistTotals(ctx) case commandBanlistAdd: - b.runBanlistAdd(ctx, commandSlice) + b.runBanlistChange(ctx, "add", commandSlice) case commandBanlistRemove: - b.runBanlistRemove(ctx, commandSlice) + b.runBanlistChange(ctx, "remove", commandSlice) case commandBanlistReset: b.runBanlistReset(ctx) case commandMailboxes: @@ -543,7 +544,7 @@ func (b *Bot) runSend(ctx context.Context) { b.runSendCommand(ctx, cfg, tos, subject, body, htmlBody) } -func (b *Bot) getSendDetails(ctx context.Context) (string, string, string, bool) { +func (b *Bot) getSendDetails(ctx context.Context) (to, subject, body string, ok bool) { evt := eventFromContext(ctx) if !b.allowSend(evt.Sender, evt.RoomID) { return "", "", "", false @@ -556,8 +557,8 @@ func (b *Bot) getSendDetails(ctx context.Context) (string, string, string, bool) } commandSlice := b.parseCommand(evt.Content.AsMessage().Body, false) - to, subject, body, err := utils.ParseSend(commandSlice) - if err == utils.ErrInvalidArgs { + to, subject, body, err = utils.ParseSend(commandSlice) + if errors.Is(err, utils.ErrInvalidArgs) { b.lp.SendNotice(evt.RoomID, fmt.Sprintf( "Usage:\n"+ "```\n"+ diff --git a/bot/command_admin.go b/bot/command_admin.go index 9056ccc..70b3f79 100644 --- a/bot/command_admin.go +++ b/bot/command_admin.go @@ -21,7 +21,7 @@ func (b *Bot) sendMailboxes(ctx context.Context) { evt := eventFromContext(ctx) mailboxes := map[string]config.Room{} slice := []string{} - b.rooms.Range(func(key any, value any) bool { + b.rooms.Range(func(key, value any) bool { if key == nil { return true } @@ -37,12 +37,12 @@ func (b *Bot) sendMailboxes(ctx context.Context) { if !ok { return true } - config, err := b.cfg.GetRoom(roomID) + cfg, err := b.cfg.GetRoom(roomID) if err != nil { b.log.Error().Err(err).Msg("cannot retrieve settings") } - mailboxes[mailbox] = config + mailboxes[mailbox] = cfg slice = append(slice, mailbox) return true }) @@ -80,7 +80,10 @@ func (b *Bot) runDelete(ctx context.Context, commandSlice []string) { b.lp.SendNotice(evt.RoomID, "mailbox does not exists, kupo", linkpearl.RelatesTo(evt.ID)) return } - roomID := v.(id.RoomID) + roomID, ok := v.(id.RoomID) + if !ok { + return + } b.rooms.Delete(mailbox) err := b.cfg.SetRoom(roomID, config.Room{}) @@ -350,7 +353,7 @@ func (b *Bot) runBanlistTotals(ctx context.Context) { b.addBanlistTimeline(ctx, true) } -func (b *Bot) runBanlistAuth(ctx context.Context, commandSlice []string) { +func (b *Bot) runBanlistAuth(ctx context.Context, commandSlice []string) { //nolint:dupl // not in that case evt := eventFromContext(ctx) cfg := b.cfg.GetBot() if len(commandSlice) < 2 { @@ -377,7 +380,7 @@ func (b *Bot) runBanlistAuth(ctx context.Context, commandSlice []string) { b.lp.SendNotice(evt.RoomID, "auth banning has been updated", linkpearl.RelatesTo(evt.ID)) } -func (b *Bot) runBanlistAuto(ctx context.Context, commandSlice []string) { +func (b *Bot) runBanlistAuto(ctx context.Context, commandSlice []string) { //nolint:dupl // not in that case evt := eventFromContext(ctx) cfg := b.cfg.GetBot() if len(commandSlice) < 2 { @@ -404,7 +407,7 @@ func (b *Bot) runBanlistAuto(ctx context.Context, commandSlice []string) { b.lp.SendNotice(evt.RoomID, "auto banning has been updated", linkpearl.RelatesTo(evt.ID)) } -func (b *Bot) runBanlistAdd(ctx context.Context, commandSlice []string) { +func (b *Bot) runBanlistChange(ctx context.Context, mode string, commandSlice []string) { evt := eventFromContext(ctx) if len(commandSlice) < 2 { b.runBanlist(ctx, commandSlice) @@ -416,37 +419,13 @@ func (b *Bot) runBanlistAdd(ctx context.Context, commandSlice []string) { } banlist := b.cfg.GetBanlist() - ips := commandSlice[1:] - for _, ip := range ips { - addr, err := net.ResolveIPAddr("ip", ip) - if err != nil { - b.Error(ctx, "cannot add %s to banlist: %v", ip, err) - return - } - banlist.Add(addr) + var action func(net.Addr) + if mode == "remove" { + action = banlist.Remove + } else { + action = banlist.Add } - err := b.cfg.SetBanlist(banlist) - if err != nil { - b.Error(ctx, "cannot set banlist: %v", err) - return - } - - b.lp.SendNotice(evt.RoomID, "banlist has been updated, kupo", linkpearl.RelatesTo(evt.ID)) -} - -func (b *Bot) runBanlistRemove(ctx context.Context, commandSlice []string) { - evt := eventFromContext(ctx) - if len(commandSlice) < 2 { - b.runBanlist(ctx, commandSlice) - return - } - if !b.cfg.GetBot().BanlistEnabled() { - b.lp.SendNotice(evt.RoomID, "banlist is disabled, you have to enable it first, kupo", linkpearl.RelatesTo(evt.ID)) - return - } - banlist := b.cfg.GetBanlist() - ips := commandSlice[1:] for _, ip := range ips { addr, err := net.ResolveIPAddr("ip", ip) @@ -454,7 +433,7 @@ func (b *Bot) runBanlistRemove(ctx context.Context, commandSlice []string) { b.Error(ctx, "cannot remove %s from banlist: %v", ip, err) return } - banlist.Remove(addr) + action(addr) } err := b.cfg.SetBanlist(banlist) diff --git a/bot/email.go b/bot/email.go index 9a6e7d7..9c0adbb 100644 --- a/bot/email.go +++ b/bot/email.go @@ -15,14 +15,12 @@ import ( "gitlab.com/etke.cc/postmoogle/utils" ) -// account data keys const ( + // account data keys acMessagePrefix = "cc.etke.postmoogle.message" acLastEventPrefix = "cc.etke.postmoogle.last" -) -// event keys -const ( + // event keys eventMessageIDkey = "cc.etke.postmoogle.messageID" eventReferencesKey = "cc.etke.postmoogle.references" eventInReplyToKey = "cc.etke.postmoogle.inReplyTo" @@ -33,6 +31,8 @@ const ( eventCcKey = "cc.etke.postmoogle.cc" ) +var ErrNoRoom = errors.New("room not found") + // SetSendmail sets mail sending func to the bot func (b *Bot) SetSendmail(sendmail func(string, string, string) error) { b.sendmail = sendmail @@ -40,8 +40,8 @@ func (b *Bot) SetSendmail(sendmail func(string, string, string) error) { } func (b *Bot) shouldQueue(msg string) bool { - errors := strings.Split(msg, ";") - for _, err := range errors { + errs := strings.Split(msg, ";") + for _, err := range errs { errParts := strings.Split(strings.TrimSpace(err), ":") if len(errParts) < 2 { continue @@ -114,10 +114,10 @@ func (b *Bot) GetIFOptions(roomID id.RoomID) email.IncomingFilteringOptions { // IncomingEmail sends incoming email to matrix room // //nolint:gocognit // TODO -func (b *Bot) IncomingEmail(ctx context.Context, email *email.Email) error { - roomID, ok := b.GetMapping(email.Mailbox(true)) +func (b *Bot) IncomingEmail(ctx context.Context, eml *email.Email) error { + roomID, ok := b.GetMapping(eml.Mailbox(true)) if !ok { - return errors.New("room not found") + return ErrNoRoom } cfg, err := b.cfg.GetRoom(roomID) if err != nil { @@ -129,18 +129,18 @@ func (b *Bot) IncomingEmail(ctx context.Context, email *email.Email) error { var threadID id.EventID newThread := true - if email.InReplyTo != "" || email.References != "" { - threadID = b.getThreadID(roomID, email.InReplyTo, email.References) + if eml.InReplyTo != "" || eml.References != "" { + threadID = b.getThreadID(roomID, eml.InReplyTo, eml.References) if threadID != "" { newThread = false ctx = threadIDToContext(ctx, threadID) - b.setThreadID(roomID, email.MessageID, threadID) + b.setThreadID(roomID, eml.MessageID, threadID) } } - content := email.Content(threadID, cfg.ContentOptions()) + content := eml.Content(threadID, cfg.ContentOptions()) eventID, serr := b.lp.Send(roomID, content) if serr != nil { - if !strings.Contains(serr.Error(), "M_UNKNOWN") { // if it's not an unknown event event error + if !strings.Contains(serr.Error(), "M_UNKNOWN") { // if it's not an unknown event error return serr } threadID = "" // unknown event edge case - remove existing thread ID to avoid complications @@ -151,15 +151,15 @@ func (b *Bot) IncomingEmail(ctx context.Context, email *email.Email) error { ctx = threadIDToContext(ctx, threadID) } - b.setThreadID(roomID, email.MessageID, threadID) + b.setThreadID(roomID, eml.MessageID, threadID) b.setLastEventID(roomID, threadID, eventID) if !cfg.NoInlines() { - b.sendFiles(ctx, roomID, email.InlineFiles, cfg.NoThreads(), threadID) + b.sendFiles(ctx, roomID, eml.InlineFiles, cfg.NoThreads(), threadID) } if !cfg.NoFiles() { - b.sendFiles(ctx, roomID, email.Files, cfg.NoThreads(), threadID) + b.sendFiles(ctx, roomID, eml.Files, cfg.NoThreads(), threadID) } if newThread && cfg.Autoreply() != "" { @@ -255,8 +255,9 @@ func (b *Bot) sendAutoreply(roomID id.RoomID, threadID id.EventID) { b.saveSentMetadata(ctx, queued, meta.ThreadID, recipients, eml, cfg, "Autoreply has been sent") } -func (b *Bot) canReply(sender id.UserID, roomID id.RoomID) bool { - return b.allowSend(sender, roomID) && b.allowReply(sender, roomID) +func (b *Bot) canReply(ctx context.Context) bool { + evt := eventFromContext(ctx) + return b.allowSend(evt.Sender, evt.RoomID) && b.allowReply(evt.Sender, evt.RoomID) } // SendEmailReply sends replies from matrix thread to email thread @@ -264,7 +265,7 @@ func (b *Bot) canReply(sender id.UserID, roomID id.RoomID) bool { //nolint:gocognit // TODO func (b *Bot) SendEmailReply(ctx context.Context) { evt := eventFromContext(ctx) - if !b.canReply(evt.Sender, evt.RoomID) { + if !b.canReply(ctx) { return } cfg, err := b.cfg.GetRoom(evt.RoomID) @@ -543,7 +544,7 @@ func (b *Bot) sendFiles(ctx context.Context, roomID id.RoomID, files []*utils.Fi } } -func (b *Bot) getThreadID(roomID id.RoomID, messageID string, references string) id.EventID { +func (b *Bot) getThreadID(roomID id.RoomID, messageID, references string) id.EventID { refs := []string{messageID} if references != "" { refs = append(refs, strings.Split(references, " ")...) @@ -592,7 +593,7 @@ func (b *Bot) getLastEventID(roomID id.RoomID, threadID id.EventID) id.EventID { return threadID } -func (b *Bot) setLastEventID(roomID id.RoomID, threadID id.EventID, eventID id.EventID) { +func (b *Bot) setLastEventID(roomID id.RoomID, threadID, eventID id.EventID) { key := acLastEventPrefix + "." + threadID.String() err := b.lp.SetRoomAccountData(roomID, key, map[string]string{"eventID": eventID.String()}) if err != nil { diff --git a/bot/reaction.go b/bot/reaction.go index 0ae40f6..186fda5 100644 --- a/bot/reaction.go +++ b/bot/reaction.go @@ -38,8 +38,7 @@ func (b *Bot) handleReaction(ctx context.Context) { ctx = threadIDToContext(ctx, threadID) linkpearl.ParseContent(evt, b.log) - switch action { - case commandSpamlistAdd: + if action == commandSpamlistAdd { sender := linkpearl.EventField[string](&srcEvt.Content, eventFromKey) if sender == "" { b.Error(ctx, "cannot get sender of the email") diff --git a/cmd/cmd.go b/cmd/cmd.go index ddf2b10..aafc72b 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -111,7 +111,6 @@ func initMatrix(cfg *config.Config) { Logger: log, }) if err != nil { - // nolint // Fatal = panic, not os.Exit() log.Fatal().Err(err).Msg("cannot initialize matrix bot") } diff --git a/email/email.go b/email/email.go index 50a6be0..8e507df 100644 --- a/email/email.go +++ b/email/email.go @@ -159,7 +159,7 @@ func (e *Email) Content(threadID id.EventID, options *ContentOptions) *event.Con } content := event.Content{ - Raw: map[string]interface{}{ + Raw: map[string]any{ options.MessageIDKey: e.MessageID, options.InReplyToKey: e.InReplyTo, options.ReferencesKey: e.References, @@ -231,7 +231,10 @@ func (e *Email) sign(domain, privkey string, data strings.Builder) string { if err != nil { return data.String() } - signer := parsedkey.(crypto.Signer) + signer, ok := parsedkey.(crypto.Signer) + if !ok { + return data.String() + } options := &dkim.SignOptions{ Domain: domain, diff --git a/email/utils.go b/email/utils.go index 6ee2a81..f9f6fbe 100644 --- a/email/utils.go +++ b/email/utils.go @@ -10,7 +10,7 @@ import ( "maunium.net/go/mautrix/id" ) -var styleRegex = regexp.MustCompile("") +var styleRegex = regexp.MustCompile("") // AddressValid checks if email address is valid func AddressValid(email string) bool { diff --git a/justfile b/justfile index 1fd3bd2..e1a0361 100644 --- a/justfile +++ b/justfile @@ -4,12 +4,6 @@ project := file_name(repo) gitlab_image := "registry." + repo + ":" + tag etke_image := replace(gitlab_image, "gitlab.com", "etke.cc") -try: - @echo {{ project }} - @echo {{ repo }} - @echo {{ gitlab_image }} - @echo {{ etke_image }} - # show help by default default: @just --list --justfile {{ justfile() }} @@ -22,7 +16,7 @@ update: go mod vendor # run linter -lint: try +lint: golangci-lint run ./... # automatically fix liter issues @@ -30,7 +24,7 @@ lintfix: golangci-lint run --fix ./... # run unit tests -test: try +test: @go test -cover -coverprofile=cover.out -coverpkg=./... -covermode=set ./... @go tool cover -func=cover.out -@rm -f cover.out @@ -44,10 +38,10 @@ build: go build -v -o {{ project }} ./cmd # docker login -login: try +login: @docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY # docker build -docker: try +docker: docker buildx create --use docker buildx build --pull --platform linux/arm64/v8,linux/amd64 --push -t {{ gitlab_image }} -t {{ etke_image }} . diff --git a/smtp/client.go b/smtp/client.go index e5e4eed..514d365 100644 --- a/smtp/client.go +++ b/smtp/client.go @@ -11,7 +11,7 @@ import ( ) type MailSender interface { - Send(from string, to string, data string) error + Send(from, to, data string) error } // SMTP client @@ -28,7 +28,7 @@ func newClient(cfg *RelayConfig, log *zerolog.Logger) *Client { } // Send email -func (c Client) Send(from string, to string, data string) error { +func (c Client) Send(from, to, data string) error { c.log.Debug().Str("from", from).Str("to", to).Msg("sending email") var conn *smtp.Client @@ -67,7 +67,7 @@ func (c Client) Send(from string, to string, data string) error { } // createDirectClient connects directly to the provided smtp host -func (c *Client) createDirectClient(from string, to string) (*smtp.Client, error) { +func (c *Client) createDirectClient(from, to string) (*smtp.Client, error) { localname := strings.SplitN(from, "@", 2)[1] target := c.config.Host + ":" + c.config.Port conn, err := smtp.Dial(target) @@ -81,8 +81,8 @@ func (c *Client) createDirectClient(from string, to string) (*smtp.Client, error } if ok, _ := conn.Extension("STARTTLS"); ok { - config := &tls.Config{ServerName: c.config.Host} - conn.StartTLS(config) //nolint:errcheck // if it doesn't work - we can't do anything anyway + config := &tls.Config{ServerName: c.config.Host} //nolint:gosec // it's smtp, even that is too strict sometimes + conn.StartTLS(config) //nolint:errcheck // if it doesn't work - we can't do anything anyway } if c.config.Usename != "" { diff --git a/smtp/logger.go b/smtp/logger.go index 1bddfbc..1968061 100644 --- a/smtp/logger.go +++ b/smtp/logger.go @@ -11,24 +11,24 @@ type validatorLoggerWrapper struct { log *zerolog.Logger } -func (l validatorLoggerWrapper) Info(msg string, args ...interface{}) { +func (l validatorLoggerWrapper) Info(msg string, args ...any) { l.log.Info().Msgf(msg, args...) } -func (l validatorLoggerWrapper) Error(msg string, args ...interface{}) { +func (l validatorLoggerWrapper) Error(msg string, args ...any) { l.log.Error().Msgf(msg, args...) } // loggerWrapper is a wrapper around any logger to implement smtp.Logger interface type loggerWrapper struct { - log func(string, ...interface{}) + log func(string, ...any) } -func (l loggerWrapper) Printf(format string, v ...interface{}) { +func (l loggerWrapper) Printf(format string, v ...any) { l.log(format, v...) } -func (l loggerWrapper) Println(v ...interface{}) { +func (l loggerWrapper) Println(v ...any) { msg := strings.Repeat("%v ", len(v)) l.log(msg, v...) } diff --git a/smtp/manager.go b/smtp/manager.go index 4a249e5..8d6175a 100644 --- a/smtp/manager.go +++ b/smtp/manager.go @@ -90,7 +90,7 @@ func NewManager(cfg *Config) *Manager { } s := smtp.NewServer(mailsrv) - s.ErrorLog = loggerWrapper{func(s string, i ...interface{}) { cfg.Logger.Error().Msgf(s, i...) }} + s.ErrorLog = loggerWrapper{func(s string, i ...any) { cfg.Logger.Error().Msgf(s, i...) }} s.ReadTimeout = 10 * time.Second s.WriteTimeout = 10 * time.Second s.MaxMessageBytes = cfg.MaxSize * 1024 * 1024 @@ -209,7 +209,7 @@ func (m *Manager) loadTLSConfig() bool { return false } - m.tls.Config = &tls.Config{Certificates: certificates} + m.tls.Config = &tls.Config{Certificates: certificates} //nolint:gosec // it's email, even that config is too strict sometimes m.smtp.TLSConfig = m.tls.Config return true } diff --git a/smtp/server.go b/smtp/server.go index 027c3b0..ac6fb91 100644 --- a/smtp/server.go +++ b/smtp/server.go @@ -10,17 +10,28 @@ import ( "gitlab.com/etke.cc/postmoogle/email" ) +const ( + // NoUserCode SMTP code + NoUserCode = 550 + // BannedCode SMTP code + BannedCode = 554 +) + var ( + // NoUserEnhancedCode enhanced SMTP code + NoUserEnhancedCode = smtp.EnhancedCode{5, 5, 0} + // BannedEnhancedCode enhanced SMTP code + BannedEnhancedCode = smtp.EnhancedCode{5, 5, 4} // ErrBanned returned to banned hosts ErrBanned = &smtp.SMTPError{ - Code: 554, - EnhancedCode: smtp.EnhancedCode{5, 5, 4}, + Code: BannedCode, + EnhancedCode: BannedEnhancedCode, Message: "please, don't bother me anymore, kupo.", } // ErrNoUser returned when no such mailbox found ErrNoUser = &smtp.SMTPError{ - Code: 550, - EnhancedCode: smtp.EnhancedCode{5, 5, 0}, + Code: NoUserCode, + EnhancedCode: NoUserEnhancedCode, Message: "no such user here, kupo.", } ) diff --git a/smtp/session.go b/smtp/session.go index fc33c54..18ca0f0 100644 --- a/smtp/session.go +++ b/smtp/session.go @@ -20,6 +20,16 @@ import ( "gitlab.com/etke.cc/postmoogle/utils" ) +// GraylistCode SMTP code +const GraylistCode = 451 + +var ( + // ErrInvalidEmail for invalid emails :) + ErrInvalidEmail = errors.New("please, provide valid email address") + // GraylistEnhancedCode is GraylistCode in enhanced code notation + GraylistEnhancedCode = smtp.EnhancedCode{4, 5, 1} +) + // incomingSession represents an SMTP-submission session receiving emails from remote servers type incomingSession struct { log *zerolog.Logger @@ -32,7 +42,7 @@ type incomingSession struct { domains []string roomID id.RoomID - ctx context.Context + ctx context.Context //nolint:containedctx // that's session addr net.Addr tos []string from string @@ -89,13 +99,13 @@ func (s *incomingSession) getAddr(envelope *enmime.Envelope) net.Addr { return s.addr } - host, portString, _ := net.SplitHostPort(addrHeader) //nolint:errcheck + host, portString, _ := net.SplitHostPort(addrHeader) //nolint:errcheck // it is real addr if host == "" { return s.addr } var port int - port, _ = strconv.Atoi(portString) //nolint:errcheck + port, _ = strconv.Atoi(portString) //nolint:errcheck // it's a real addr realAddr := &net.TCPAddr{IP: net.ParseIP(host), Port: port} s.log.Info().Str("addr", realAddr.String()).Msg("real address") @@ -115,7 +125,7 @@ func (s *incomingSession) Data(r io.Reader) error { return err } addr := s.getAddr(envelope) - reader.Seek(0, io.SeekStart) //nolint:errcheck + reader.Seek(0, io.SeekStart) //nolint:errcheck // becase we're sure that's ok validations := s.getFilters(s.roomID) if !validateIncoming(s.from, s.tos[0], addr, s.log, validations) { s.ban(addr) @@ -123,8 +133,8 @@ func (s *incomingSession) Data(r io.Reader) error { } if s.greylisted(addr) { return &smtp.SMTPError{ - Code: 451, - EnhancedCode: smtp.EnhancedCode{4, 5, 1}, + Code: GraylistCode, + EnhancedCode: GraylistEnhancedCode, Message: "You have been greylisted, try again a bit later.", } } @@ -164,16 +174,16 @@ type outgoingSession struct { domains []string getRoomID func(string) (id.RoomID, bool) - ctx context.Context + ctx context.Context //nolint:containedctx // that's session tos []string from string fromRoom id.RoomID } -func (s *outgoingSession) Mail(from string, opts smtp.MailOptions) error { +func (s *outgoingSession) Mail(from string, _ smtp.MailOptions) error { sentry.GetHubFromContext(s.ctx).Scope().SetTag("from", from) if !email.AddressValid(from) { - return errors.New("please, provide email address") + return ErrInvalidEmail } hostname := utils.Hostname(from) var domainok bool @@ -234,7 +244,7 @@ func validateIncoming(from, to string, senderAddr net.Addr, log *zerolog.Logger, case *net.TCPAddr: sender = netaddr.IP default: - host, _, _ := net.SplitHostPort(senderAddr.String()) // nolint:errcheck + host, _, _ := net.SplitHostPort(senderAddr.String()) //nolint:errcheck // interface constraints sender = net.ParseIP(host) } diff --git a/utils/command.go b/utils/command.go index 4fe48bf..dc5f9be 100644 --- a/utils/command.go +++ b/utils/command.go @@ -5,21 +5,24 @@ import ( "strings" ) +// MinSendCommandParts is minimal count of space-separated parts for !pm send command +const MinSendCommandParts = 3 + // ErrInvalidArgs returned when a command's arguments are invalid var ErrInvalidArgs = fmt.Errorf("invalid arguments") // ParseSend parses "!pm send" command, returns to, subject, body, err -func ParseSend(commandSlice []string) (string, string, string, error) { +func ParseSend(commandSlice []string) (to, subject, body string, err error) { message := strings.Join(commandSlice, " ") lines := strings.Split(message, "\n") - if len(lines) < 3 { + if len(lines) < MinSendCommandParts { return "", "", "", ErrInvalidArgs } commandSlice = strings.Split(lines[0], " ") - to := commandSlice[1] - subject := lines[1] - body := strings.Join(lines[2:], "\n") + to = commandSlice[1] + subject = lines[1] + body = strings.Join(lines[2:], "\n") return to, subject, body, nil } diff --git a/utils/mail.go b/utils/mail.go index 79ef9d8..c4f67c4 100644 --- a/utils/mail.go +++ b/utils/mail.go @@ -25,8 +25,7 @@ func Hostname(email string) string { } // EmailParts parses email address into mailbox, subaddress, and hostname -func EmailParts(email string) (string, string, string) { - var mailbox, hostname string +func EmailParts(email string) (mailbox, sub, hostname string) { address, err := emailaddress.Parse(email) if err == nil { mailbox = address.LocalPart @@ -44,7 +43,6 @@ func EmailParts(email string) (string, string, string) { } } - var sub string idx := strings.Index(mailbox, "+") if idx != -1 { sub = strings.ReplaceAll(mailbox[idx:], "+", "") @@ -54,7 +52,7 @@ func EmailParts(email string) (string, string, string) { } // EmailsList returns human-readable list of mailbox's emails for all available domains -func EmailsList(mailbox string, domain string) string { +func EmailsList(mailbox, domain string) string { var msg strings.Builder domain = SanitizeDomain(domain) msg.WriteString(mailbox)