feat(bimi): logotipo do dominio validado, alojado e publicado no DNS
A foto de perfil nao viaja: nem o Gmail nem o Outlook leem uma imagem por utilizador do servidor de quem envia, e nao existe protocolo que o permita. O avatar que esses mostram vem dos contactos de quem recebe ou da conta Google ou Microsoft do remetente. O que eles leem e BIMI, que e por dominio. Fica implementado: validacao, alojamento e o registo. O validador reporta todas as violacoes de uma vez, e nao a primeira. Um receptor que recusa a marca nao explica nada -- simplesmente nao desenha -- por isso descobrir as regras uma viagem de cada vez seria a pior maneira possivel de aprender este formato. Sao verificados o baseProfile e a versao que os receptores procuram, o viewBox quadrado (uma marca nao quadrada e cortada de forma diferente por cada cliente), exactamente um title, e a ausencia de tudo o que seja activo ou externo: script, handlers de evento, links, imagens rasterizadas, animacoes e referencias para fora do documento. O registo so e publicado quando ha mesmo uma marca guardada. Um registo que aponta para um logotipo inexistente e pior do que nenhum, porque o receptor para de procurar assim que encontra um. O logotipo e servido aberto, porque quem o vai buscar sao os fornecedores de correio a avaliar uma mensagem e esses nunca terao sessao aqui. Fica escrito no README que o Gmail e o Outlook so desenham a marca com um VMC, e o painel avisa quando nao ha nenhum configurado. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fcfee6a3f9
commit
6a602ab88f
16 files changed
+955
-5
No files matched your search
@@ -35,6 +35,15 @@ type DnsRecordSpec struct {
|
||||
// this domain. Without it every message the relay forwards fails SPF
|
||||
// (PLAN.md section 10.4).
|
||||
RelaySpfInclude string
|
||||
// BimiLogoURL is where the published mark is served from. Empty when the
|
||||
// domain has no logo, and then no BIMI record is published at all: an
|
||||
// advertised record pointing at nothing is worse than none, because a
|
||||
// receiver stops looking once it finds one.
|
||||
BimiLogoURL string
|
||||
// BimiVmcURL is the Verified Mark Certificate. Gmail and Outlook render
|
||||
// the logo only when the record carries one; without it the record is
|
||||
// still valid and other clients may use it.
|
||||
BimiVmcURL string
|
||||
}
|
||||
|
||||
// BuildDnsRecordSpecs generates the DNS records of PLAN.md section 7: the 13
|
||||
@@ -196,6 +205,21 @@ func BuildDnsRecordSpecs(spec DnsRecordSpec) []DnsRecord {
|
||||
})
|
||||
}
|
||||
|
||||
// BIMI, published only when there is a logo to point at.
|
||||
if spec.BimiLogoURL != "" {
|
||||
value := fmt.Sprintf("v=BIMI1; l=%s;", spec.BimiLogoURL)
|
||||
if spec.BimiVmcURL != "" {
|
||||
value = fmt.Sprintf("v=BIMI1; l=%s; a=%s;", spec.BimiLogoURL, spec.BimiVmcURL)
|
||||
}
|
||||
records = append(records, DnsRecord{
|
||||
Type: "TXT",
|
||||
Name: fmt.Sprintf("default._bimi.%s", domainName),
|
||||
Value: value,
|
||||
TTL: 1,
|
||||
Comment: "LambdaMail BIMI Record",
|
||||
})
|
||||
}
|
||||
|
||||
// 2. Conditional AAAA record if IPv6 is supplied
|
||||
if ownsMailHost && serverIPv6 != "" {
|
||||
records = append(records, DnsRecord{
|
||||
|
||||
@@ -195,3 +195,72 @@ func TestBuildDnsRecordSpecs_KeepsMailHostAddressesInsideTheDomain(t *testing.T)
|
||||
t.Errorf("lost the mail host addresses (A=%v AAAA=%v)", a, aaaa)
|
||||
}
|
||||
}
|
||||
|
||||
// BIMI is published only when there is a mark to point at.
|
||||
//
|
||||
// A record that advertises a logo which is not there is worse than no record:
|
||||
// a receiver stops looking once it finds one, so an empty pointer buys a
|
||||
// failed fetch instead of the fallback it would otherwise have used.
|
||||
func TestBuildDnsRecordSpecs_BimiOnlyWhenThereIsALogo(t *testing.T) {
|
||||
without := BuildDnsRecordSpecs(DnsRecordSpec{
|
||||
DomainName: "example.test",
|
||||
MailHost: "mail.example.test",
|
||||
ServerIPv4: "192.0.2.1",
|
||||
})
|
||||
for _, r := range without {
|
||||
if strings.Contains(r.Name, "_bimi") {
|
||||
t.Errorf("published %s with no logo configured", r.Name)
|
||||
}
|
||||
}
|
||||
|
||||
with := BuildDnsRecordSpecs(DnsRecordSpec{
|
||||
DomainName: "example.test",
|
||||
MailHost: "mail.example.test",
|
||||
ServerIPv4: "192.0.2.1",
|
||||
BimiLogoURL: "https://mail.example.test/.well-known/bimi/default.svg",
|
||||
})
|
||||
|
||||
var found *DnsRecord
|
||||
for i, r := range with {
|
||||
if r.Name == "default._bimi.example.test" {
|
||||
found = &with[i]
|
||||
}
|
||||
}
|
||||
if found == nil {
|
||||
t.Fatal("no BIMI record was published for a domain with a logo")
|
||||
}
|
||||
if found.Type != "TXT" {
|
||||
t.Errorf("BIMI record type is %q, want TXT", found.Type)
|
||||
}
|
||||
if !strings.HasPrefix(found.Value, "v=BIMI1;") {
|
||||
t.Errorf("value %q does not start with the version tag", found.Value)
|
||||
}
|
||||
// No certificate configured, so no a= tag: an empty one would advertise a
|
||||
// certificate that cannot be fetched.
|
||||
if strings.Contains(found.Value, "a=") {
|
||||
t.Errorf("value %q carries an empty certificate tag", found.Value)
|
||||
}
|
||||
}
|
||||
|
||||
// Gmail and Outlook render the mark only when the record points at a Verified
|
||||
// Mark Certificate, so the tag has to be carried when there is one.
|
||||
func TestBuildDnsRecordSpecs_BimiCarriesTheCertificateWhenConfigured(t *testing.T) {
|
||||
specs := BuildDnsRecordSpecs(DnsRecordSpec{
|
||||
DomainName: "example.test",
|
||||
MailHost: "mail.example.test",
|
||||
ServerIPv4: "192.0.2.1",
|
||||
BimiLogoURL: "https://mail.example.test/.well-known/bimi/default.svg",
|
||||
BimiVmcURL: "https://mail.example.test/.well-known/bimi/default.pem",
|
||||
})
|
||||
|
||||
for _, r := range specs {
|
||||
if r.Name != "default._bimi.example.test" {
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(r.Value, "a=https://mail.example.test/.well-known/bimi/default.pem") {
|
||||
t.Errorf("value %q does not carry the certificate", r.Value)
|
||||
}
|
||||
return
|
||||
}
|
||||
t.Fatal("no BIMI record was published")
|
||||
}
|
||||
Reference in new issue
Block a user