Harden contract validation, STARTTLS edge cases, and rule output

This commit is contained in:
nemunaire 2026-04-26 16:39:22 +07:00
commit fa212f0fae
9 changed files with 104 additions and 39 deletions

View file

@ -31,6 +31,9 @@ func starttlsXMPP(conn net.Conn, sni, ns string) error {
dec := xml.NewDecoder(conn)
// Read the inbound <stream:stream> opening and its <stream:features>.
// A peer that opens with <stream:error/> (or anything other than features)
// is not going to advertise STARTTLS: surface that immediately rather
// than spinning on tokens until the deadline fires.
hasStartTLS := false
outer:
for {
@ -38,29 +41,38 @@ outer:
if err != nil {
return fmt.Errorf("read stream features: %w", err)
}
if se, ok := tok.(xml.StartElement); ok {
if se.Name.Local == "features" {
// Scan features children.
for {
t2, err := dec.Token()
if err != nil {
return fmt.Errorf("read features body: %w", err)
se, ok := tok.(xml.StartElement)
if !ok {
continue
}
switch se.Name.Local {
case "stream":
// Outer <stream:stream> opening. Continue reading children.
continue
case "features":
for {
t2, err := dec.Token()
if err != nil {
return fmt.Errorf("read features body: %w", err)
}
switch ee := t2.(type) {
case xml.StartElement:
if ee.Name.Local == "starttls" {
hasStartTLS = true
}
switch ee := t2.(type) {
case xml.StartElement:
if ee.Name.Local == "starttls" {
hasStartTLS = true
}
if err := dec.Skip(); err != nil {
return fmt.Errorf("skip feature %q: %w", ee.Name.Local, err)
}
case xml.EndElement:
if ee.Name.Local == "features" {
break outer
}
if err := dec.Skip(); err != nil {
return fmt.Errorf("skip feature %q: %w", ee.Name.Local, err)
}
case xml.EndElement:
if ee.Name.Local == "features" {
break outer
}
}
}
case "error":
return fmt.Errorf("server returned <stream:error/> before features")
default:
return fmt.Errorf("%w: unexpected element %q before features", errStartTLSNotOffered, se.Name.Local)
}
}
if !hasStartTLS {