go-firewalld/caps.go
2026-07-02 14:08:37 -05:00

88 lines
2.7 KiB
Go

package firewalld
import (
"strconv"
"strings"
)
// Capability names a version-gated feature of the firewalld D-Bus surface.
// Callers test support with Conn.Supports; internal code version-gates transports
// (tuple vs dict) on these flags.
type Capability int
const (
// DictZoneSettings gates the a{sv} zone-settings path (getSettings2/update2 on
// config.zone, getZoneSettings2/setZoneSettings2 on the runtime .zone). Added
// in firewalld 0.9. Absent on EL7 0.6.3, which uses the v1 tuple only.
DictZoneSettings Capability = iota
// Policies gates zone-to-zone policy objects (config listPolicies/addPolicy,
// runtime getPolicySettings/getActivePolicies). Added in 0.9. Absent on EL7.
Policies
// ServiceSettings2 gates getServiceSettings2/addService2 (a{sv} services).
// Present on 1.3.4, absent on 0.6.3.
ServiceSettings2
// AddZone2 gates config.addZone2/addService2 (dict-based creation). Added 0.9.
AddZone2
// ResetToDefaults gates the main resetToDefaults method. Present on 1.x.
ResetToDefaults
)
// Version is a parsed firewalld version. Missing components read as zero, so a
// server reporting "1" compares equal to "1.0.0".
type Version struct {
Major, Minor, Patch int
Raw string
}
// parseVersion parses a "X.Y.Z" firewalld version string. Trailing non-numeric
// suffixes on a component (e.g. a distro tag) are tolerated by reading the leading
// digits. Unparseable components read as zero rather than failing the connection.
func parseVersion(raw string) Version {
v := Version{Raw: raw}
parts := strings.SplitN(raw, ".", 3)
dst := []*int{&v.Major, &v.Minor, &v.Patch}
for i := 0; i < len(parts) && i < 3; i++ {
*dst[i] = leadingInt(parts[i])
}
return v
}
// leadingInt reads the leading base-10 digits of s, ignoring any suffix.
func leadingInt(s string) int {
end := 0
for end < len(s) && s[end] >= '0' && s[end] <= '9' {
end++
}
if end == 0 {
return 0
}
n, err := strconv.Atoi(s[:end])
if err != nil {
return 0
}
return n
}
// atLeast reports whether v is >= the given major.minor.
func (v Version) atLeast(major, minor int) bool {
if v.Major != major {
return v.Major > major
}
return v.Minor >= minor
}
// String returns the raw version string as reported by the server.
func (v Version) String() string { return v.Raw }
// capsFor derives the capability set from a parsed version. Gates follow the
// introspection findings: dict settings and policies land at 0.9; the *2 service
// APIs and resetToDefaults are 1.x.
func capsFor(v Version) map[Capability]bool {
return map[Capability]bool{
DictZoneSettings: v.atLeast(0, 9),
Policies: v.atLeast(0, 9),
AddZone2: v.atLeast(0, 9),
ServiceSettings2: v.atLeast(1, 0),
ResetToDefaults: v.atLeast(1, 0),
}
}