diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9346dcd --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,21 @@ +name: CI +on: + push: + branches: + - main + pull_request: + +permissions: + contents: read + +jobs: + golangci: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-go@v6 + with: + go-version: stable + - name: golangci-lint + uses: golangci/golangci-lint-action@v9 diff --git a/cmd/yggmail/main.go b/cmd/yggmail/main.go index 777e62d..304c0cd 100644 --- a/cmd/yggmail/main.go +++ b/cmd/yggmail/main.go @@ -57,7 +57,7 @@ func main() { smtpaddr := flag.String("smtp", "localhost:1025", "SMTP listen address") imapaddr := flag.String("imap", "localhost:1143", "IMAP listen address") multicast := flag.Bool("multicast", false, "Connect to Yggdrasil peers on your LAN") - mcastregexp := flag.String("mcastregexp", ".*", "Regexp for multicast") + mcastregexp := flag.String("mcastregexp", ".*", "Regexp for multicast") password := flag.Bool("password", false, "Set a new IMAP/SMTP password") passwordhash := flag.String("passwordhash", "", "Set a new IMAP/SMTP password (hash)") flag.Var(&peerAddrs, "peer", "Connect to a specific Yggdrasil static peer (this option can be given more than once)") @@ -77,7 +77,7 @@ func main() { if err != nil { panic(err) } - defer storage.Close() + defer storage.Close() // nolint:errcheck log.Printf("Using database file %q\n", *database) skStr, err := storage.ConfigGet("private_key") @@ -128,7 +128,7 @@ func main() { log.Println("The supplied passwords do not match") os.Exit(1) } - + // trim away whitespace of UTF-8 bytes now as string finalPassword := strings.TrimSpace(string(password1)) @@ -146,19 +146,19 @@ func main() { os.Exit(0) case passwordhash != nil && *passwordhash != "": - var hash string = strings.TrimSpace(*passwordhash); + var hash = strings.TrimSpace(*passwordhash) if len(hash) == 0 { - log.Println("Password hash cannot be blank"); - os.Exit(1); + log.Println("Password hash cannot be blank") + os.Exit(1) } - - log.Printf("Using password hash: '%v'\n", hash); + + log.Printf("Using password hash: '%v'\n", hash) if _, err := bcrypt.Cost(([]byte)(hash)); err != nil { - log.Printf("The provided hash is invalid %v\n", err); - os.Exit(1); + log.Printf("The provided hash is invalid %v\n", err) + os.Exit(1) } else if err := storage.ConfigSetPassword(hash); err != nil { - log.Println("Failed to set password: ", err); + log.Println("Failed to set password: ", err) os.Exit(1) } diff --git a/internal/imapserver/backend.go b/internal/imapserver/backend.go index aa78303..7745b3c 100644 --- a/internal/imapserver/backend.go +++ b/internal/imapserver/backend.go @@ -31,7 +31,7 @@ func (b *Backend) Login(conn *imap.ConnInfo, username, password string) (backend // If our username is email-like, then take just the localpart if pk, err := utils.ParseAddress(username); err == nil { if !pk.Equal(b.Config.PublicKey) { - b.Log.Println("Failed to authenticate IMAP user due to wrong domain", pk, b.Config.PublicKey) + b.Log.Println("Failed to authenticate IMAP user due to wrong domain", hex.EncodeToString(pk), hex.EncodeToString(b.Config.PublicKey)) return nil, fmt.Errorf("failed to authenticate: wrong domain in username") } } diff --git a/internal/imapserver/mailbox.go b/internal/imapserver/mailbox.go index c7265cc..531d81c 100644 --- a/internal/imapserver/mailbox.go +++ b/internal/imapserver/mailbox.go @@ -327,7 +327,9 @@ func (mbox *Mailbox) MoveMessages(uid bool, seqset *imap.SeqSet, dest string) er return err } if mbox.name == "Outbox" { - mbox.backend.Storage.QueueDeleteDestinationForID("Outbox", int(id)) + if err := mbox.backend.Storage.QueueDeleteDestinationForID("Outbox", int(id)); err != nil { + return err + } } } return nil diff --git a/internal/smtpsender/sender.go b/internal/smtpsender/sender.go index 34b6cb7..40b32f6 100644 --- a/internal/smtpsender/sender.go +++ b/internal/smtpsender/sender.go @@ -120,7 +120,9 @@ func (q *Queue) run() { _, mail, err := q.queues.Storage.MailSelect("Outbox", ref.ID) if err != nil { if errors.Is(err, sql.ErrNoRows) { - q.queues.Storage.QueueDeleteDestinationForID("Outbox", ref.ID) + if err = q.queues.Storage.QueueDeleteDestinationForID("Outbox", ref.ID); err != nil { + q.queues.Log.Println("Failed delete queue destination for ID", ref.ID, "due to error:", err) + } } else { q.queues.Log.Println("Failed to get mail", ref.ID, "due to error:", err) } @@ -134,13 +136,13 @@ func (q *Queue) run() { if err != nil { return fmt.Errorf("q.queues.Transport.Dial: %w", err) } - defer conn.Close() + defer conn.Close() // nolint:errcheck client, err := smtp.NewClient(conn, q.destination) if err != nil { return fmt.Errorf("smtp.NewClient: %w", err) } - defer client.Close() + defer client.Close() // nolint:errcheck if err := client.Hello(hex.EncodeToString(q.queues.Config.PublicKey)); err != nil { q.queues.Log.Println("Remote server", q.destination, "did not accept HELLO:", err) @@ -161,7 +163,7 @@ func (q *Queue) run() { if err != nil { return fmt.Errorf("client.Data: %w", err) } - defer writer.Close() + defer writer.Close() // nolint:errcheck if _, err := writer.Write(mail.Mail); err != nil { return fmt.Errorf("writer.Write: %w", err) diff --git a/internal/smtpserver/backend.go b/internal/smtpserver/backend.go index a469e9d..fd7113b 100644 --- a/internal/smtpserver/backend.go +++ b/internal/smtpserver/backend.go @@ -62,16 +62,16 @@ func (b *Backend) Login(state *smtp.ConnectionState, username, password string) }, nil case BackendModeExternal: - return nil, fmt.Errorf("Not expecting authenticated connection on external backend") + return nil, fmt.Errorf("not expecting authenticated connection on external backend") } - return nil, fmt.Errorf("Authenticated login failed") + return nil, fmt.Errorf("authenticated login failed") } func (b *Backend) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session, error) { switch b.Mode { case BackendModeInternal: - return nil, fmt.Errorf("Not expecting anonymous connection on internal backend") + return nil, fmt.Errorf("not expecting anonymous connection on internal backend") case BackendModeExternal: // The connection came from our overlay listener, so we should check @@ -82,7 +82,7 @@ func (b *Backend) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session, err } remote := hex.EncodeToString(pks) if state.Hostname != remote { - return nil, fmt.Errorf("You are not who you claim to be") + return nil, fmt.Errorf("you are not who you claim to be") } b.Log.Println("Incoming SMTP session from", remote) @@ -93,5 +93,5 @@ func (b *Backend) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session, err }, nil } - return nil, fmt.Errorf("Anonymous login failed") + return nil, fmt.Errorf("anonymous login failed") } diff --git a/internal/storage/sqlite3/sqlite3.go b/internal/storage/sqlite3/sqlite3.go index 38a61cf..d64675d 100644 --- a/internal/storage/sqlite3/sqlite3.go +++ b/internal/storage/sqlite3/sqlite3.go @@ -86,7 +86,7 @@ func (w *Writer) Do(db *sql.DB, txn *sql.Tx, f func(txn *sql.Tx) error) error { } func (w *Writer) run() { - if !w.running.CAS(false, true) { + if !w.running.CompareAndSwap(false, true) { return } defer w.running.Store(false) diff --git a/internal/storage/sqlite3/table_mailboxes.go b/internal/storage/sqlite3/table_mailboxes.go index 34cb146..71b19e1 100644 --- a/internal/storage/sqlite3/table_mailboxes.go +++ b/internal/storage/sqlite3/table_mailboxes.go @@ -110,7 +110,7 @@ func (t *TableMailboxes) MailboxList(onlySubscribed bool) ([]string, error) { if err != nil { return nil, fmt.Errorf("t.listMailboxes.Query: %w", err) } - defer rows.Close() + defer rows.Close() // nolint:errcheck var mailboxes []string for rows.Next() { var mailbox string diff --git a/internal/storage/sqlite3/table_mails.go b/internal/storage/sqlite3/table_mails.go index 5e76b87..f2edc98 100644 --- a/internal/storage/sqlite3/table_mails.go +++ b/internal/storage/sqlite3/table_mails.go @@ -200,7 +200,7 @@ func (t *TableMails) MailSearch(mailbox string) ([]uint32, error) { if err != nil { return nil, fmt.Errorf("t.searchMail.Query: %w", err) } - defer rows.Close() + defer rows.Close() // nolint:errcheck for rows.Next() { var id uint32 if err := rows.Scan(&id); err != nil { diff --git a/internal/storage/sqlite3/table_queue.go b/internal/storage/sqlite3/table_queue.go index 41265c2..9441c49 100644 --- a/internal/storage/sqlite3/table_queue.go +++ b/internal/storage/sqlite3/table_queue.go @@ -98,7 +98,7 @@ func (t *TableQueue) QueueListDestinations() ([]string, error) { } return nil, fmt.Errorf("t.queueSelectDestinations.Query: %w", err) } - defer rows.Close() + defer rows.Close() // nolint:errcheck var destinations []string for rows.Next() { var destination string @@ -118,7 +118,7 @@ func (t *TableQueue) QueueMailIDsForDestination(destination string) ([]types.Que } return nil, fmt.Errorf("t.queueSelectDestinations.Query: %w", err) } - defer rows.Close() + defer rows.Close() // nolint:errcheck var ids []types.QueuedMail for rows.Next() { var id int