lint fixes

This commit is contained in:
Aine
2023-10-03 00:03:29 +03:00
parent ef221038f7
commit 0d0dcf20b9
18 changed files with 196 additions and 152 deletions

View File

@@ -4,77 +4,123 @@ run:
issues-exit-code: 1 issues-exit-code: 1
tests: true tests: true
build-tags: [] build-tags: []
skip-dirs: [] skip-dirs:
- mocks
skip-dirs-use-default: true skip-dirs-use-default: true
skip-files: [] skip-files: []
modules-download-mode: readonly modules-download-mode: readonly
allow-parallel-runners: false
output: output:
format: colored-line-number format: colored-line-number
print-issued-lines: true print-issued-lines: true
print-linter-name: true print-linter-name: true
uniq-by-line: true
path-prefix: ""
sort-results: true sort-results: true
linters-settings: linters-settings:
decorder:
dec-order:
- const
- var
- type
- func
dogsled:
max-blank-identifiers: 3
errcheck: errcheck:
check-type-assertions: true
check-blank: true check-blank: true
errchkjson:
report-no-exported: true
exhaustive:
check:
- switch
- map
default-signifies-exhaustive: true
gocognit: gocognit:
min-complexity: 15 min-complexity: 15
nestif: nestif:
min-complexity: 4 min-complexity: 5
gocritic: gocritic:
enabled-tags: enabled-tags:
- diagnostic
- style
- performance - performance
gofmt:
simplify: true
rewrite-rules:
- pattern: 'interface{}'
replacement: 'any'
- pattern: 'a[b:len(a)]'
replacement: 'a[b:]'
gofumpt: gofumpt:
lang-version: "1.18" extra-rules: true
gosimple:
go: "1.18"
checks: [ "all" ]
govet: govet:
check-shadowing: true check-shadowing: true
enable: grouper:
- atomicalign const-require-single-const: true
- shadow import-require-single-import: true
var-require-single-var: true
misspell: misspell:
locale: US locale: US
staticcheck: usestdlibvars:
go: "1.18" time-month: true
checks: [ "all" ] time-layout: true
stylecheck: crypto-hash: true
go: "1.18" default-rpc-path: true
os-dev-null: true
sql-isolation-level: true
tls-signature-scheme: true
constant-kind: true
unparam: unparam:
check-exported: true check-exported: true
unused:
go: "1.18"
gci:
sections:
- standard
- default
- prefix(gitlab.com/etke.cc/postmoogle)
section-separators:
- newLine
linters: linters:
disable-all: false disable-all: false
enable: enable:
- megacheck - asasalint
- govet - asciicheck
- bidichk
- bodyclose
- containedctx
- decorder
- dogsled
- dupl
- dupword
- durationcheck
- errcheck - errcheck
- gci - errchkjson
- errname
- errorlint
- execinquery
- exhaustive
- exportloopref
- forcetypeassert
- gochecknoinits
- gocognit - gocognit
- nestif - gocritic
# - gocritic # ref: https://github.com/golangci/golangci-lint/issues/2649#issue-1170906525 - gocyclo
- goerr113
- gofmt
- gofumpt - gofumpt
- goimports - goimports
- gosec
- gosimple - gosimple
- gosmopolitan
- govet - govet
- ineffassign
- makezero
- mirror
- misspell - misspell
- nestif
- nolintlint
- prealloc
- predeclared
- revive
- sqlclosecheck
- staticcheck - staticcheck
- stylecheck - unconvert
- unparam - unparam
- unused - unused
- usestdlibvars
- wastedassign
fast: false fast: false

View File

@@ -32,7 +32,7 @@ func (b *Bot) allowUsers(actorID id.UserID) bool {
return true return true
} }
func (b *Bot) allowAnyone(actorID id.UserID, targetRoomID id.RoomID) bool { func (b *Bot) allowAnyone(_ id.UserID, _ id.RoomID) bool {
return true return true
} }
@@ -54,7 +54,7 @@ func (b *Bot) allowOwner(actorID id.UserID, targetRoomID id.RoomID) bool {
return owner == actorID.String() 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) return mxidwc.Match(actorID.String(), b.allowedAdmins)
} }

View File

@@ -91,14 +91,14 @@ func New(
} }
// Error message to the log and matrix room // 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) evt := eventFromContext(ctx)
threadID := threadIDFromContext(ctx) threadID := threadIDFromContext(ctx)
if threadID == "" { if threadID == "" {
threadID = linkpearl.EventParent(evt.ID, evt.Content.AsMessage()) 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()) b.log.Error().Err(err).Msg(err.Error())
if evt == nil { if evt == nil {
return return

View File

@@ -2,6 +2,7 @@ package bot
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"strings" "strings"
"time" "time"
@@ -372,7 +373,7 @@ func (b *Bot) handle(ctx context.Context) {
if err != nil { if err != nil {
b.log.Error().Err(err).Msg("cannot send typing notification") 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) { if !cmd.allowed(evt.Sender, evt.RoomID) {
b.lp.SendNotice(evt.RoomID, "not allowed to do that, kupo") b.lp.SendNotice(evt.RoomID, "not allowed to do that, kupo")
@@ -413,9 +414,9 @@ func (b *Bot) handle(ctx context.Context) {
case commandBanlistTotals: case commandBanlistTotals:
b.runBanlistTotals(ctx) b.runBanlistTotals(ctx)
case commandBanlistAdd: case commandBanlistAdd:
b.runBanlistAdd(ctx, commandSlice) b.runBanlistChange(ctx, "add", commandSlice)
case commandBanlistRemove: case commandBanlistRemove:
b.runBanlistRemove(ctx, commandSlice) b.runBanlistChange(ctx, "remove", commandSlice)
case commandBanlistReset: case commandBanlistReset:
b.runBanlistReset(ctx) b.runBanlistReset(ctx)
case commandMailboxes: case commandMailboxes:
@@ -543,7 +544,7 @@ func (b *Bot) runSend(ctx context.Context) {
b.runSendCommand(ctx, cfg, tos, subject, body, htmlBody) 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) evt := eventFromContext(ctx)
if !b.allowSend(evt.Sender, evt.RoomID) { if !b.allowSend(evt.Sender, evt.RoomID) {
return "", "", "", false 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) commandSlice := b.parseCommand(evt.Content.AsMessage().Body, false)
to, subject, body, err := utils.ParseSend(commandSlice) to, subject, body, err = utils.ParseSend(commandSlice)
if err == utils.ErrInvalidArgs { if errors.Is(err, utils.ErrInvalidArgs) {
b.lp.SendNotice(evt.RoomID, fmt.Sprintf( b.lp.SendNotice(evt.RoomID, fmt.Sprintf(
"Usage:\n"+ "Usage:\n"+
"```\n"+ "```\n"+

View File

@@ -21,7 +21,7 @@ func (b *Bot) sendMailboxes(ctx context.Context) {
evt := eventFromContext(ctx) evt := eventFromContext(ctx)
mailboxes := map[string]config.Room{} mailboxes := map[string]config.Room{}
slice := []string{} slice := []string{}
b.rooms.Range(func(key any, value any) bool { b.rooms.Range(func(key, value any) bool {
if key == nil { if key == nil {
return true return true
} }
@@ -37,12 +37,12 @@ func (b *Bot) sendMailboxes(ctx context.Context) {
if !ok { if !ok {
return true return true
} }
config, err := b.cfg.GetRoom(roomID) cfg, err := b.cfg.GetRoom(roomID)
if err != nil { if err != nil {
b.log.Error().Err(err).Msg("cannot retrieve settings") b.log.Error().Err(err).Msg("cannot retrieve settings")
} }
mailboxes[mailbox] = config mailboxes[mailbox] = cfg
slice = append(slice, mailbox) slice = append(slice, mailbox)
return true 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)) b.lp.SendNotice(evt.RoomID, "mailbox does not exists, kupo", linkpearl.RelatesTo(evt.ID))
return return
} }
roomID := v.(id.RoomID) roomID, ok := v.(id.RoomID)
if !ok {
return
}
b.rooms.Delete(mailbox) b.rooms.Delete(mailbox)
err := b.cfg.SetRoom(roomID, config.Room{}) err := b.cfg.SetRoom(roomID, config.Room{})
@@ -350,7 +353,7 @@ func (b *Bot) runBanlistTotals(ctx context.Context) {
b.addBanlistTimeline(ctx, true) 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) evt := eventFromContext(ctx)
cfg := b.cfg.GetBot() cfg := b.cfg.GetBot()
if len(commandSlice) < 2 { 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)) 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) evt := eventFromContext(ctx)
cfg := b.cfg.GetBot() cfg := b.cfg.GetBot()
if len(commandSlice) < 2 { 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)) 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) evt := eventFromContext(ctx)
if len(commandSlice) < 2 { if len(commandSlice) < 2 {
b.runBanlist(ctx, commandSlice) b.runBanlist(ctx, commandSlice)
@@ -416,37 +419,13 @@ func (b *Bot) runBanlistAdd(ctx context.Context, commandSlice []string) {
} }
banlist := b.cfg.GetBanlist() banlist := b.cfg.GetBanlist()
ips := commandSlice[1:] var action func(net.Addr)
for _, ip := range ips { if mode == "remove" {
addr, err := net.ResolveIPAddr("ip", ip) action = banlist.Remove
if err != nil { } else {
b.Error(ctx, "cannot add %s to banlist: %v", ip, err) action = banlist.Add
return
}
banlist.Add(addr)
} }
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:] ips := commandSlice[1:]
for _, ip := range ips { for _, ip := range ips {
addr, err := net.ResolveIPAddr("ip", ip) 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) b.Error(ctx, "cannot remove %s from banlist: %v", ip, err)
return return
} }
banlist.Remove(addr) action(addr)
} }
err := b.cfg.SetBanlist(banlist) err := b.cfg.SetBanlist(banlist)

View File

@@ -15,14 +15,12 @@ import (
"gitlab.com/etke.cc/postmoogle/utils" "gitlab.com/etke.cc/postmoogle/utils"
) )
// account data keys
const ( const (
// account data keys
acMessagePrefix = "cc.etke.postmoogle.message" acMessagePrefix = "cc.etke.postmoogle.message"
acLastEventPrefix = "cc.etke.postmoogle.last" acLastEventPrefix = "cc.etke.postmoogle.last"
)
// event keys // event keys
const (
eventMessageIDkey = "cc.etke.postmoogle.messageID" eventMessageIDkey = "cc.etke.postmoogle.messageID"
eventReferencesKey = "cc.etke.postmoogle.references" eventReferencesKey = "cc.etke.postmoogle.references"
eventInReplyToKey = "cc.etke.postmoogle.inReplyTo" eventInReplyToKey = "cc.etke.postmoogle.inReplyTo"
@@ -33,6 +31,8 @@ const (
eventCcKey = "cc.etke.postmoogle.cc" eventCcKey = "cc.etke.postmoogle.cc"
) )
var ErrNoRoom = errors.New("room not found")
// SetSendmail sets mail sending func to the bot // SetSendmail sets mail sending func to the bot
func (b *Bot) SetSendmail(sendmail func(string, string, string) error) { func (b *Bot) SetSendmail(sendmail func(string, string, string) error) {
b.sendmail = sendmail b.sendmail = sendmail
@@ -40,8 +40,8 @@ func (b *Bot) SetSendmail(sendmail func(string, string, string) error) {
} }
func (b *Bot) shouldQueue(msg string) bool { func (b *Bot) shouldQueue(msg string) bool {
errors := strings.Split(msg, ";") errs := strings.Split(msg, ";")
for _, err := range errors { for _, err := range errs {
errParts := strings.Split(strings.TrimSpace(err), ":") errParts := strings.Split(strings.TrimSpace(err), ":")
if len(errParts) < 2 { if len(errParts) < 2 {
continue continue
@@ -114,10 +114,10 @@ func (b *Bot) GetIFOptions(roomID id.RoomID) email.IncomingFilteringOptions {
// IncomingEmail sends incoming email to matrix room // IncomingEmail sends incoming email to matrix room
// //
//nolint:gocognit // TODO //nolint:gocognit // TODO
func (b *Bot) IncomingEmail(ctx context.Context, email *email.Email) error { func (b *Bot) IncomingEmail(ctx context.Context, eml *email.Email) error {
roomID, ok := b.GetMapping(email.Mailbox(true)) roomID, ok := b.GetMapping(eml.Mailbox(true))
if !ok { if !ok {
return errors.New("room not found") return ErrNoRoom
} }
cfg, err := b.cfg.GetRoom(roomID) cfg, err := b.cfg.GetRoom(roomID)
if err != nil { if err != nil {
@@ -129,18 +129,18 @@ func (b *Bot) IncomingEmail(ctx context.Context, email *email.Email) error {
var threadID id.EventID var threadID id.EventID
newThread := true newThread := true
if email.InReplyTo != "" || email.References != "" { if eml.InReplyTo != "" || eml.References != "" {
threadID = b.getThreadID(roomID, email.InReplyTo, email.References) threadID = b.getThreadID(roomID, eml.InReplyTo, eml.References)
if threadID != "" { if threadID != "" {
newThread = false newThread = false
ctx = threadIDToContext(ctx, threadID) 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) eventID, serr := b.lp.Send(roomID, content)
if serr != nil { 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 return serr
} }
threadID = "" // unknown event edge case - remove existing thread ID to avoid complications 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) ctx = threadIDToContext(ctx, threadID)
} }
b.setThreadID(roomID, email.MessageID, threadID) b.setThreadID(roomID, eml.MessageID, threadID)
b.setLastEventID(roomID, threadID, eventID) b.setLastEventID(roomID, threadID, eventID)
if !cfg.NoInlines() { if !cfg.NoInlines() {
b.sendFiles(ctx, roomID, email.InlineFiles, cfg.NoThreads(), threadID) b.sendFiles(ctx, roomID, eml.InlineFiles, cfg.NoThreads(), threadID)
} }
if !cfg.NoFiles() { 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() != "" { 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") b.saveSentMetadata(ctx, queued, meta.ThreadID, recipients, eml, cfg, "Autoreply has been sent")
} }
func (b *Bot) canReply(sender id.UserID, roomID id.RoomID) bool { func (b *Bot) canReply(ctx context.Context) bool {
return b.allowSend(sender, roomID) && b.allowReply(sender, roomID) 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 // 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 //nolint:gocognit // TODO
func (b *Bot) SendEmailReply(ctx context.Context) { func (b *Bot) SendEmailReply(ctx context.Context) {
evt := eventFromContext(ctx) evt := eventFromContext(ctx)
if !b.canReply(evt.Sender, evt.RoomID) { if !b.canReply(ctx) {
return return
} }
cfg, err := b.cfg.GetRoom(evt.RoomID) 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} refs := []string{messageID}
if references != "" { if references != "" {
refs = append(refs, strings.Split(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 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() key := acLastEventPrefix + "." + threadID.String()
err := b.lp.SetRoomAccountData(roomID, key, map[string]string{"eventID": eventID.String()}) err := b.lp.SetRoomAccountData(roomID, key, map[string]string{"eventID": eventID.String()})
if err != nil { if err != nil {

View File

@@ -38,8 +38,7 @@ func (b *Bot) handleReaction(ctx context.Context) {
ctx = threadIDToContext(ctx, threadID) ctx = threadIDToContext(ctx, threadID)
linkpearl.ParseContent(evt, b.log) linkpearl.ParseContent(evt, b.log)
switch action { if action == commandSpamlistAdd {
case commandSpamlistAdd:
sender := linkpearl.EventField[string](&srcEvt.Content, eventFromKey) sender := linkpearl.EventField[string](&srcEvt.Content, eventFromKey)
if sender == "" { if sender == "" {
b.Error(ctx, "cannot get sender of the email") b.Error(ctx, "cannot get sender of the email")

View File

@@ -111,7 +111,6 @@ func initMatrix(cfg *config.Config) {
Logger: log, Logger: log,
}) })
if err != nil { if err != nil {
// nolint // Fatal = panic, not os.Exit()
log.Fatal().Err(err).Msg("cannot initialize matrix bot") log.Fatal().Err(err).Msg("cannot initialize matrix bot")
} }

View File

@@ -159,7 +159,7 @@ func (e *Email) Content(threadID id.EventID, options *ContentOptions) *event.Con
} }
content := event.Content{ content := event.Content{
Raw: map[string]interface{}{ Raw: map[string]any{
options.MessageIDKey: e.MessageID, options.MessageIDKey: e.MessageID,
options.InReplyToKey: e.InReplyTo, options.InReplyToKey: e.InReplyTo,
options.ReferencesKey: e.References, options.ReferencesKey: e.References,
@@ -231,7 +231,10 @@ func (e *Email) sign(domain, privkey string, data strings.Builder) string {
if err != nil { if err != nil {
return data.String() return data.String()
} }
signer := parsedkey.(crypto.Signer) signer, ok := parsedkey.(crypto.Signer)
if !ok {
return data.String()
}
options := &dkim.SignOptions{ options := &dkim.SignOptions{
Domain: domain, Domain: domain,

View File

@@ -10,7 +10,7 @@ import (
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
) )
var styleRegex = regexp.MustCompile("<style((.|\n|\r)*?)<\\/style>") var styleRegex = regexp.MustCompile("<style((.|\n|\r)*?)</style>")
// AddressValid checks if email address is valid // AddressValid checks if email address is valid
func AddressValid(email string) bool { func AddressValid(email string) bool {

View File

@@ -4,12 +4,6 @@ project := file_name(repo)
gitlab_image := "registry." + repo + ":" + tag gitlab_image := "registry." + repo + ":" + tag
etke_image := replace(gitlab_image, "gitlab.com", "etke.cc") etke_image := replace(gitlab_image, "gitlab.com", "etke.cc")
try:
@echo {{ project }}
@echo {{ repo }}
@echo {{ gitlab_image }}
@echo {{ etke_image }}
# show help by default # show help by default
default: default:
@just --list --justfile {{ justfile() }} @just --list --justfile {{ justfile() }}
@@ -22,7 +16,7 @@ update:
go mod vendor go mod vendor
# run linter # run linter
lint: try lint:
golangci-lint run ./... golangci-lint run ./...
# automatically fix liter issues # automatically fix liter issues
@@ -30,7 +24,7 @@ lintfix:
golangci-lint run --fix ./... golangci-lint run --fix ./...
# run unit tests # run unit tests
test: try test:
@go test -cover -coverprofile=cover.out -coverpkg=./... -covermode=set ./... @go test -cover -coverprofile=cover.out -coverpkg=./... -covermode=set ./...
@go tool cover -func=cover.out @go tool cover -func=cover.out
-@rm -f cover.out -@rm -f cover.out
@@ -44,10 +38,10 @@ build:
go build -v -o {{ project }} ./cmd go build -v -o {{ project }} ./cmd
# docker login # docker login
login: try login:
@docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY @docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
# docker build # docker build
docker: try docker:
docker buildx create --use docker buildx create --use
docker buildx build --pull --platform linux/arm64/v8,linux/amd64 --push -t {{ gitlab_image }} -t {{ etke_image }} . docker buildx build --pull --platform linux/arm64/v8,linux/amd64 --push -t {{ gitlab_image }} -t {{ etke_image }} .

View File

@@ -11,7 +11,7 @@ import (
) )
type MailSender interface { type MailSender interface {
Send(from string, to string, data string) error Send(from, to, data string) error
} }
// SMTP client // SMTP client
@@ -28,7 +28,7 @@ func newClient(cfg *RelayConfig, log *zerolog.Logger) *Client {
} }
// Send email // 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") c.log.Debug().Str("from", from).Str("to", to).Msg("sending email")
var conn *smtp.Client 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 // 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] localname := strings.SplitN(from, "@", 2)[1]
target := c.config.Host + ":" + c.config.Port target := c.config.Host + ":" + c.config.Port
conn, err := smtp.Dial(target) 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 { if ok, _ := conn.Extension("STARTTLS"); ok {
config := &tls.Config{ServerName: c.config.Host} 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 conn.StartTLS(config) //nolint:errcheck // if it doesn't work - we can't do anything anyway
} }
if c.config.Usename != "" { if c.config.Usename != "" {

View File

@@ -11,24 +11,24 @@ type validatorLoggerWrapper struct {
log *zerolog.Logger 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...) 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...) l.log.Error().Msgf(msg, args...)
} }
// loggerWrapper is a wrapper around any logger to implement smtp.Logger interface // loggerWrapper is a wrapper around any logger to implement smtp.Logger interface
type loggerWrapper struct { 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...) l.log(format, v...)
} }
func (l loggerWrapper) Println(v ...interface{}) { func (l loggerWrapper) Println(v ...any) {
msg := strings.Repeat("%v ", len(v)) msg := strings.Repeat("%v ", len(v))
l.log(msg, v...) l.log(msg, v...)
} }

View File

@@ -90,7 +90,7 @@ func NewManager(cfg *Config) *Manager {
} }
s := smtp.NewServer(mailsrv) 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.ReadTimeout = 10 * time.Second
s.WriteTimeout = 10 * time.Second s.WriteTimeout = 10 * time.Second
s.MaxMessageBytes = cfg.MaxSize * 1024 * 1024 s.MaxMessageBytes = cfg.MaxSize * 1024 * 1024
@@ -209,7 +209,7 @@ func (m *Manager) loadTLSConfig() bool {
return false 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 m.smtp.TLSConfig = m.tls.Config
return true return true
} }

View File

@@ -10,17 +10,28 @@ import (
"gitlab.com/etke.cc/postmoogle/email" "gitlab.com/etke.cc/postmoogle/email"
) )
const (
// NoUserCode SMTP code
NoUserCode = 550
// BannedCode SMTP code
BannedCode = 554
)
var ( 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 returned to banned hosts
ErrBanned = &smtp.SMTPError{ ErrBanned = &smtp.SMTPError{
Code: 554, Code: BannedCode,
EnhancedCode: smtp.EnhancedCode{5, 5, 4}, EnhancedCode: BannedEnhancedCode,
Message: "please, don't bother me anymore, kupo.", Message: "please, don't bother me anymore, kupo.",
} }
// ErrNoUser returned when no such mailbox found // ErrNoUser returned when no such mailbox found
ErrNoUser = &smtp.SMTPError{ ErrNoUser = &smtp.SMTPError{
Code: 550, Code: NoUserCode,
EnhancedCode: smtp.EnhancedCode{5, 5, 0}, EnhancedCode: NoUserEnhancedCode,
Message: "no such user here, kupo.", Message: "no such user here, kupo.",
} }
) )

View File

@@ -20,6 +20,16 @@ import (
"gitlab.com/etke.cc/postmoogle/utils" "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 // incomingSession represents an SMTP-submission session receiving emails from remote servers
type incomingSession struct { type incomingSession struct {
log *zerolog.Logger log *zerolog.Logger
@@ -32,7 +42,7 @@ type incomingSession struct {
domains []string domains []string
roomID id.RoomID roomID id.RoomID
ctx context.Context ctx context.Context //nolint:containedctx // that's session
addr net.Addr addr net.Addr
tos []string tos []string
from string from string
@@ -89,13 +99,13 @@ func (s *incomingSession) getAddr(envelope *enmime.Envelope) net.Addr {
return s.addr return s.addr
} }
host, portString, _ := net.SplitHostPort(addrHeader) //nolint:errcheck host, portString, _ := net.SplitHostPort(addrHeader) //nolint:errcheck // it is real addr
if host == "" { if host == "" {
return s.addr return s.addr
} }
var port int 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} realAddr := &net.TCPAddr{IP: net.ParseIP(host), Port: port}
s.log.Info().Str("addr", realAddr.String()).Msg("real address") s.log.Info().Str("addr", realAddr.String()).Msg("real address")
@@ -115,7 +125,7 @@ func (s *incomingSession) Data(r io.Reader) error {
return err return err
} }
addr := s.getAddr(envelope) 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) validations := s.getFilters(s.roomID)
if !validateIncoming(s.from, s.tos[0], addr, s.log, validations) { if !validateIncoming(s.from, s.tos[0], addr, s.log, validations) {
s.ban(addr) s.ban(addr)
@@ -123,8 +133,8 @@ func (s *incomingSession) Data(r io.Reader) error {
} }
if s.greylisted(addr) { if s.greylisted(addr) {
return &smtp.SMTPError{ return &smtp.SMTPError{
Code: 451, Code: GraylistCode,
EnhancedCode: smtp.EnhancedCode{4, 5, 1}, EnhancedCode: GraylistEnhancedCode,
Message: "You have been greylisted, try again a bit later.", Message: "You have been greylisted, try again a bit later.",
} }
} }
@@ -164,16 +174,16 @@ type outgoingSession struct {
domains []string domains []string
getRoomID func(string) (id.RoomID, bool) getRoomID func(string) (id.RoomID, bool)
ctx context.Context ctx context.Context //nolint:containedctx // that's session
tos []string tos []string
from string from string
fromRoom id.RoomID 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) sentry.GetHubFromContext(s.ctx).Scope().SetTag("from", from)
if !email.AddressValid(from) { if !email.AddressValid(from) {
return errors.New("please, provide email address") return ErrInvalidEmail
} }
hostname := utils.Hostname(from) hostname := utils.Hostname(from)
var domainok bool var domainok bool
@@ -234,7 +244,7 @@ func validateIncoming(from, to string, senderAddr net.Addr, log *zerolog.Logger,
case *net.TCPAddr: case *net.TCPAddr:
sender = netaddr.IP sender = netaddr.IP
default: default:
host, _, _ := net.SplitHostPort(senderAddr.String()) // nolint:errcheck host, _, _ := net.SplitHostPort(senderAddr.String()) //nolint:errcheck // interface constraints
sender = net.ParseIP(host) sender = net.ParseIP(host)
} }

View File

@@ -5,21 +5,24 @@ import (
"strings" "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 // ErrInvalidArgs returned when a command's arguments are invalid
var ErrInvalidArgs = fmt.Errorf("invalid arguments") var ErrInvalidArgs = fmt.Errorf("invalid arguments")
// ParseSend parses "!pm send" command, returns to, subject, body, err // 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, " ") message := strings.Join(commandSlice, " ")
lines := strings.Split(message, "\n") lines := strings.Split(message, "\n")
if len(lines) < 3 { if len(lines) < MinSendCommandParts {
return "", "", "", ErrInvalidArgs return "", "", "", ErrInvalidArgs
} }
commandSlice = strings.Split(lines[0], " ") commandSlice = strings.Split(lines[0], " ")
to := commandSlice[1] to = commandSlice[1]
subject := lines[1] subject = lines[1]
body := strings.Join(lines[2:], "\n") body = strings.Join(lines[2:], "\n")
return to, subject, body, nil return to, subject, body, nil
} }

View File

@@ -25,8 +25,7 @@ func Hostname(email string) string {
} }
// EmailParts parses email address into mailbox, subaddress, and hostname // EmailParts parses email address into mailbox, subaddress, and hostname
func EmailParts(email string) (string, string, string) { func EmailParts(email string) (mailbox, sub, hostname string) {
var mailbox, hostname string
address, err := emailaddress.Parse(email) address, err := emailaddress.Parse(email)
if err == nil { if err == nil {
mailbox = address.LocalPart mailbox = address.LocalPart
@@ -44,7 +43,6 @@ func EmailParts(email string) (string, string, string) {
} }
} }
var sub string
idx := strings.Index(mailbox, "+") idx := strings.Index(mailbox, "+")
if idx != -1 { if idx != -1 {
sub = strings.ReplaceAll(mailbox[idx:], "+", "") 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 // 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 var msg strings.Builder
domain = SanitizeDomain(domain) domain = SanitizeDomain(domain)
msg.WriteString(mailbox) msg.WriteString(mailbox)