package db import ( "database/sql" "errors" ) // CreatePGPIdentity adds a new PGP identity for a mailbox — a mailbox may hold // several at once (see esrv_mailbox_pgp_identities in schema.go). func (d *DB) CreatePGPIdentity(mailboxID int64, label, email, fingerprint, publicKeyArmor, privateKeyArmor string) (int64, error) { res, err := d.Exec(` INSERT INTO esrv_mailbox_pgp_identities (mailbox_id, label, email, fingerprint, public_key_armor, private_key_armor) VALUES (?, ?, ?, ?, ?, ?) `, mailboxID, label, email, fingerprint, publicKeyArmor, privateKeyArmor) if err != nil { return 0, err } return res.LastInsertId() } // ListPGPIdentities returns a mailbox's PGP identities, most recent first. func (d *DB) ListPGPIdentities(mailboxID int64) ([]MailboxPGPIdentity, error) { rows, err := d.Query(`SELECT id, mailbox_id, label, email, fingerprint, public_key_armor, private_key_armor, created_at FROM esrv_mailbox_pgp_identities WHERE mailbox_id = ? ORDER BY created_at DESC, id DESC`, mailboxID) if err != nil { return nil, err } defer rows.Close() var out []MailboxPGPIdentity for rows.Next() { var id MailboxPGPIdentity if err := rows.Scan(&id.ID, &id.MailboxID, &id.Label, &id.Email, &id.Fingerprint, &id.PublicKeyArmor, &id.PrivateKeyArmor, &id.CreatedAt); err != nil { return nil, err } out = append(out, id) } return out, rows.Err() } // GetPGPIdentity returns nil, nil if no such identity exists for this mailbox — // scoped to mailboxID so one mailbox owner can't reach another's identity by // guessing its ID. func (d *DB) GetPGPIdentity(mailboxID, identityID int64) (*MailboxPGPIdentity, error) { row := d.QueryRow(`SELECT id, mailbox_id, label, email, fingerprint, public_key_armor, private_key_armor, created_at FROM esrv_mailbox_pgp_identities WHERE mailbox_id = ? AND id = ?`, mailboxID, identityID) var id MailboxPGPIdentity if err := row.Scan(&id.ID, &id.MailboxID, &id.Label, &id.Email, &id.Fingerprint, &id.PublicKeyArmor, &id.PrivateKeyArmor, &id.CreatedAt); err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, nil } return nil, err } return &id, nil } // DeletePGPIdentity removes one identity, scoped to mailboxID. func (d *DB) DeletePGPIdentity(mailboxID, identityID int64) error { _, err := d.Exec(`DELETE FROM esrv_mailbox_pgp_identities WHERE mailbox_id = ? AND id = ?`, mailboxID, identityID) return err } // UpsertPGPContact adds a contact's PGP public key, replacing any existing key // already on file for that email (e.g. after the contact rotates their key). func (d *DB) UpsertPGPContact(mailboxID int64, email, label, fingerprint, publicKeyArmor string) error { _, err := d.Exec(` INSERT INTO esrv_mailbox_pgp_contacts (mailbox_id, email, label, fingerprint, public_key_armor) VALUES (?, ?, ?, ?, ?) ON CONFLICT(mailbox_id, email) DO UPDATE SET label = excluded.label, fingerprint = excluded.fingerprint, public_key_armor = excluded.public_key_armor `, mailboxID, email, label, fingerprint, publicKeyArmor) return err } // GetPGPContact returns nil, nil if no key is on file for that email. func (d *DB) GetPGPContact(mailboxID int64, email string) (*MailboxPGPContact, error) { row := d.QueryRow(`SELECT id, mailbox_id, email, label, public_key_armor, fingerprint, created_at FROM esrv_mailbox_pgp_contacts WHERE mailbox_id = ? AND email = ?`, mailboxID, email) var c MailboxPGPContact if err := row.Scan(&c.ID, &c.MailboxID, &c.Email, &c.Label, &c.PublicKeyArmor, &c.Fingerprint, &c.CreatedAt); err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, nil } return nil, err } return &c, nil } // GetPGPContactByID returns nil, nil if no such contact exists for this mailbox — // scoped to mailboxID so one mailbox owner can't reach another's contact by guessing // its ID. Used by compose's recipient-key picker, which selects contacts by ID // rather than matching a To/Cc/Bcc address against GetPGPContact's stored email. func (d *DB) GetPGPContactByID(mailboxID, contactID int64) (*MailboxPGPContact, error) { row := d.QueryRow(`SELECT id, mailbox_id, email, label, public_key_armor, fingerprint, created_at FROM esrv_mailbox_pgp_contacts WHERE mailbox_id = ? AND id = ?`, mailboxID, contactID) var c MailboxPGPContact if err := row.Scan(&c.ID, &c.MailboxID, &c.Email, &c.Label, &c.PublicKeyArmor, &c.Fingerprint, &c.CreatedAt); err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, nil } return nil, err } return &c, nil } // ListPGPContacts returns a mailbox's collected contact keys, alphabetical by email. func (d *DB) ListPGPContacts(mailboxID int64) ([]MailboxPGPContact, error) { rows, err := d.Query(`SELECT id, mailbox_id, email, label, public_key_armor, fingerprint, created_at FROM esrv_mailbox_pgp_contacts WHERE mailbox_id = ? ORDER BY email`, mailboxID) if err != nil { return nil, err } defer rows.Close() var out []MailboxPGPContact for rows.Next() { var c MailboxPGPContact if err := rows.Scan(&c.ID, &c.MailboxID, &c.Email, &c.Label, &c.PublicKeyArmor, &c.Fingerprint, &c.CreatedAt); err != nil { return nil, err } out = append(out, c) } return out, rows.Err() } // DeletePGPContact removes one contact key, scoped to mailboxID so one mailbox // owner can't delete another's contact by guessing its ID. func (d *DB) DeletePGPContact(mailboxID, contactID int64) error { _, err := d.Exec(`DELETE FROM esrv_mailbox_pgp_contacts WHERE mailbox_id = ? AND id = ?`, mailboxID, contactID) return err }