- Bridge Node Support: Added functionality to manage mail (send/receive) on remote nodes via a bridge node. Full transparent management requires a private key on the bridge, while the remote node can operate with only a public key. - End-to-End Encryption (E2EE): Integrated E2EE using age. Encryption and decryption occur strictly at the bridge level; node administrators cannot access message content. - New Addressing Schema: Transitioned from nodepub@yggmail to userpub@nodepub. - Key Adaptation: Implemented ed25519 to x25519 coordinate adaptation, allowing signing keys to be used for encryption. Changed - Address Encoding: Switched from hex16 to base32 for the address space. - Memory Optimization: Replaced direct memory allocations with streaming for mail processing to reduce RAM overhead. - Code Refactoring: Performed decomposition of the original inherited codebase to improve maintainability. Fixed - Mail Updates: Fixed the mail retrieval mechanism to ensure near-instant updates for incoming messages. Changes to be committed: modified: .gitignore modified: README.md modified: cmd/yggmail/main.go modified: go.mod modified: go.sum new file: internal/account/manager.go new file: internal/account/session.go modified: internal/imapserver/backend.go modified: internal/imapserver/imap.go modified: internal/imapserver/mailbox.go modified: internal/imapserver/notify.go modified: internal/imapserver/user.go new file: internal/notify/notify.go new file: internal/notify/remote/remote.go new file: internal/remote/client.go new file: internal/remote/handlers.go new file: internal/remote/io.go new file: internal/remote/server.go new file: internal/remote/sign.go new file: internal/remote/types/request.pb.go new file: internal/remote/types/request.proto new file: internal/remote/types/response.pb.go new file: internal/remote/types/response.proto new file: internal/shell/shell.go modified: internal/smtpsender/sender.go modified: internal/smtpserver/backend.go modified: internal/smtpserver/session_local.go modified: internal/smtpserver/session_remote.go modified: internal/smtpserver/smtp.go new file: internal/storage/filestore/filestore.go new file: internal/storage/local/storage.go new file: internal/storage/remote/mail.go new file: internal/storage/remote/mailbox.go new file: internal/storage/remote/queue.go new file: internal/storage/remote/storage.go new file: internal/storage/remote/watch.go modified: internal/storage/sqlite3/sqlite3.go new file: internal/storage/sqlite3/table_accounts.go modified: internal/storage/sqlite3/table_mailboxes.go modified: internal/storage/sqlite3/table_mails.go modified: internal/storage/sqlite3/table_queue.go modified: internal/storage/storage.go modified: internal/storage/types/types.go modified: internal/utils/address.go new file: internal/utils/age/age.go new file: internal/utils/age/bech32/bech32.go new file: internal/utils/crypt.go new file: internal/utils/e2ee/crypt.go new file: internal/utils/e2ee/e2ee_test.go new file: internal/utils/e2ee/msg.go new file: internal/utils/x25519.go modified: internal/welcome/welcome.go
176 lines
4.9 KiB
Go
176 lines
4.9 KiB
Go
/*
|
|
* Copyright (c) 2024 Your Name / Yggmail Contributor
|
|
* (Original Copyright (c) 2021 Neil Alexander)
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
package sqlite3
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
)
|
|
|
|
type TableMailboxes struct {
|
|
db *sql.DB
|
|
writer *Writer
|
|
selectMailboxes *sql.Stmt
|
|
listMailboxes *sql.Stmt
|
|
listMailboxesSubscribed *sql.Stmt
|
|
createMailbox *sql.Stmt
|
|
renameMailbox *sql.Stmt
|
|
deleteMailbox *sql.Stmt
|
|
subscribeMailbox *sql.Stmt
|
|
}
|
|
|
|
// Мы изменили схему: PRIMARY KEY теперь составной (аккаунт + папка)
|
|
const mailboxesSchema = `
|
|
CREATE TABLE IF NOT EXISTS mailboxes (
|
|
account_pubkey TEXT NOT NULL,
|
|
mailbox TEXT NOT NULL DEFAULT('INBOX'),
|
|
subscribed BOOLEAN NOT NULL DEFAULT 1,
|
|
PRIMARY KEY(account_pubkey, mailbox),
|
|
FOREIGN KEY(account_pubkey) REFERENCES accounts(pubkey) ON DELETE CASCADE
|
|
);
|
|
`
|
|
|
|
const mailboxesList = `
|
|
SELECT mailbox FROM mailboxes WHERE account_pubkey = $1
|
|
`
|
|
|
|
const mailboxesListSubscribed = `
|
|
SELECT mailbox FROM mailboxes WHERE account_pubkey = $1 AND subscribed = 1
|
|
`
|
|
|
|
const mailboxesSelect = `
|
|
SELECT mailbox FROM mailboxes WHERE account_pubkey = $1 AND mailbox = $2
|
|
`
|
|
|
|
const mailboxesCreate = `
|
|
INSERT OR IGNORE INTO mailboxes (account_pubkey, mailbox) VALUES($1, $2)
|
|
`
|
|
|
|
const mailboxesRename = `
|
|
UPDATE mailboxes SET mailbox = $2 WHERE account_pubkey = $1 AND mailbox = $3
|
|
`
|
|
|
|
const mailboxesDelete = `
|
|
DELETE FROM mailboxes WHERE account_pubkey = $1 AND mailbox = $2
|
|
`
|
|
|
|
const mailboxesSubscribe = `
|
|
UPDATE mailboxes SET subscribed = $2 WHERE account_pubkey = $1 AND mailbox = $3
|
|
`
|
|
|
|
func NewTableMailboxes(db *sql.DB, writer *Writer) (*TableMailboxes, error) {
|
|
t := &TableMailboxes{
|
|
db: db,
|
|
writer: writer,
|
|
}
|
|
_, err := db.Exec(mailboxesSchema)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db.Exec: %w", err)
|
|
}
|
|
t.listMailboxes, err = db.Prepare(mailboxesList)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db.Prepare(mailboxesList): %w", err)
|
|
}
|
|
t.listMailboxesSubscribed, err = db.Prepare(mailboxesListSubscribed)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db.Prepare(mailboxesListSubscribed): %w", err)
|
|
}
|
|
t.selectMailboxes, err = db.Prepare(mailboxesSelect)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db.Prepare(mailboxesSelect): %w", err)
|
|
}
|
|
t.createMailbox, err = db.Prepare(mailboxesCreate)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db.Prepare(mailboxesCreate): %w", err)
|
|
}
|
|
t.deleteMailbox, err = db.Prepare(mailboxesDelete)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db.Prepare(mailboxesDelete): %w", err)
|
|
}
|
|
t.renameMailbox, err = db.Prepare(mailboxesRename)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db.Prepare(mailboxesRename): %w", err)
|
|
}
|
|
t.subscribeMailbox, err = db.Prepare(mailboxesSubscribe)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db.Prepare(mailboxesSubscribe): %w", err)
|
|
}
|
|
return t, nil
|
|
}
|
|
|
|
// Теперь все методы принимают accountPubKey первым аргументом
|
|
|
|
func (t *TableMailboxes) MailboxList(accountPubKey string, onlySubscribed bool) ([]string, error) {
|
|
stmt := t.listMailboxes
|
|
if onlySubscribed {
|
|
stmt = t.listMailboxesSubscribed
|
|
}
|
|
rows, err := stmt.Query(accountPubKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("t.listMailboxes.Query: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var mailboxes []string
|
|
for rows.Next() {
|
|
var mailbox string
|
|
if err := rows.Scan(&mailbox); err != nil {
|
|
return nil, fmt.Errorf("rows.Scan: %w", err)
|
|
}
|
|
mailboxes = append(mailboxes, mailbox)
|
|
}
|
|
return mailboxes, nil
|
|
}
|
|
|
|
func (t *TableMailboxes) MailboxSelect(accountPubKey string, mailbox string) (bool, error) {
|
|
row := t.selectMailboxes.QueryRow(accountPubKey, mailbox)
|
|
var got string
|
|
err := row.Scan(&got)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return false, nil
|
|
}
|
|
return false, fmt.Errorf("row.Scan: %w", err)
|
|
}
|
|
return mailbox == got, nil
|
|
}
|
|
|
|
func (t *TableMailboxes) MailboxCreate(accountPubKey string, name string) error {
|
|
return t.writer.Do(t.db, nil, func(txn *sql.Tx) error {
|
|
_, err := t.createMailbox.Exec(accountPubKey, name)
|
|
return err
|
|
})
|
|
}
|
|
|
|
func (t *TableMailboxes) MailboxRename(accountPubKey string, old, new string) error {
|
|
return t.writer.Do(t.db, nil, func(txn *sql.Tx) error {
|
|
_, err := t.renameMailbox.Exec(accountPubKey, new, old)
|
|
return err
|
|
})
|
|
}
|
|
|
|
func (t *TableMailboxes) MailboxDelete(accountPubKey string, name string) error {
|
|
return t.writer.Do(t.db, nil, func(txn *sql.Tx) error {
|
|
_, err := t.deleteMailbox.Exec(accountPubKey, name)
|
|
return err
|
|
})
|
|
}
|
|
|
|
func (t *TableMailboxes) MailboxSubscribe(accountPubKey string, name string, subscribed bool) error {
|
|
sn := 1
|
|
if !subscribed {
|
|
sn = 0
|
|
}
|
|
return t.writer.Do(t.db, nil, func(txn *sql.Tx) error {
|
|
_, err := t.subscribeMailbox.Exec(accountPubKey, sn, name)
|
|
return err
|
|
})
|
|
}
|