package db import ( "database/sql" "errors" "time" ) // CreateSMIMEIdentity adds a new S/MIME identity for a mailbox — a mailbox may hold // several at once (see esrv_mailbox_smime_identities in schema.go). func (d *DB) CreateSMIMEIdentity(mailboxID int64, certPEM, keyPEM string, notAfter time.Time) (int64, error) { res, err := d.Exec(` INSERT INTO esrv_mailbox_smime_identities (mailbox_id, cert_pem, key_pem, not_after) VALUES (?, ?, ?, ?) `, mailboxID, certPEM, keyPEM, notAfter) if err != nil { return 0, err } return res.LastInsertId() } // ListSMIMEIdentities returns a mailbox's S/MIME identities, most recent first. func (d *DB) ListSMIMEIdentities(mailboxID int64) ([]MailboxSMIMEIdentity, error) { rows, err := d.Query(`SELECT id, mailbox_id, cert_pem, key_pem, not_after, created_at FROM esrv_mailbox_smime_identities WHERE mailbox_id = ? ORDER BY created_at DESC, id DESC`, mailboxID) if err != nil { return nil, err } defer rows.Close() var out []MailboxSMIMEIdentity for rows.Next() { var id MailboxSMIMEIdentity if err := rows.Scan(&id.ID, &id.MailboxID, &id.CertPEM, &id.KeyPEM, &id.NotAfter, &id.CreatedAt); err != nil { return nil, err } out = append(out, id) } return out, rows.Err() } // GetSMIMEIdentity 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) GetSMIMEIdentity(mailboxID, identityID int64) (*MailboxSMIMEIdentity, error) { row := d.QueryRow(`SELECT id, mailbox_id, cert_pem, key_pem, not_after, created_at FROM esrv_mailbox_smime_identities WHERE mailbox_id = ? AND id = ?`, mailboxID, identityID) var id MailboxSMIMEIdentity if err := row.Scan(&id.ID, &id.MailboxID, &id.CertPEM, &id.KeyPEM, &id.NotAfter, &id.CreatedAt); err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, nil } return nil, err } return &id, nil } // DeleteSMIMEIdentity removes one identity, scoped to mailboxID. func (d *DB) DeleteSMIMEIdentity(mailboxID, identityID int64) error { _, err := d.Exec(`DELETE FROM esrv_mailbox_smime_identities WHERE mailbox_id = ? AND id = ?`, mailboxID, identityID) return err } // UpsertSMIMEContact adds a contact certificate, replacing any existing certificate // already on file for that email (e.g. after the contact renews their cert). func (d *DB) UpsertSMIMEContact(mailboxID int64, email, certPEM string) error { _, err := d.Exec(` INSERT INTO esrv_mailbox_smime_contacts (mailbox_id, email, cert_pem) VALUES (?, ?, ?) ON CONFLICT(mailbox_id, email) DO UPDATE SET cert_pem = excluded.cert_pem `, mailboxID, email, certPEM) return err } // GetSMIMEContact returns nil, nil if no certificate is on file for that email. func (d *DB) GetSMIMEContact(mailboxID int64, email string) (*MailboxSMIMEContact, error) { row := d.QueryRow(`SELECT id, mailbox_id, email, cert_pem, created_at FROM esrv_mailbox_smime_contacts WHERE mailbox_id = ? AND email = ?`, mailboxID, email) var c MailboxSMIMEContact if err := row.Scan(&c.ID, &c.MailboxID, &c.Email, &c.CertPEM, &c.CreatedAt); err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, nil } return nil, err } return &c, nil } // ListSMIMEContacts returns a mailbox's collected contact certificates, alphabetical // by email. func (d *DB) ListSMIMEContacts(mailboxID int64) ([]MailboxSMIMEContact, error) { rows, err := d.Query(`SELECT id, mailbox_id, email, cert_pem, created_at FROM esrv_mailbox_smime_contacts WHERE mailbox_id = ? ORDER BY email`, mailboxID) if err != nil { return nil, err } defer rows.Close() var out []MailboxSMIMEContact for rows.Next() { var c MailboxSMIMEContact if err := rows.Scan(&c.ID, &c.MailboxID, &c.Email, &c.CertPEM, &c.CreatedAt); err != nil { return nil, err } out = append(out, c) } return out, rows.Err() } // DeleteSMIMEContact removes one contact certificate, scoped to mailboxID so one // mailbox owner can't delete another's contact by guessing its ID. func (d *DB) DeleteSMIMEContact(mailboxID, contactID int64) error { _, err := d.Exec(`DELETE FROM esrv_mailbox_smime_contacts WHERE mailbox_id = ? AND id = ?`, mailboxID, contactID) return err }