Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
84 lines
3.3 KiB
Go
84 lines
3.3 KiB
Go
package simagent
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/gosnmp/gosnmp"
|
|
)
|
|
|
|
const timeWindow = 150 // seconds, RFC 3414
|
|
|
|
func (a *Agent) initUsers() error {
|
|
tbl := gosnmp.NewSnmpV3SecurityParametersTable(gosnmp.Logger{})
|
|
// The empty user decodes engine-ID discovery probes.
|
|
probe := &gosnmp.UsmSecurityParameters{AuthenticationProtocol: gosnmp.NoAuth, PrivacyProtocol: gosnmp.NoPriv}
|
|
if err := tbl.Add("", probe); err != nil {
|
|
return err
|
|
}
|
|
for _, u := range a.cfg.V3 {
|
|
if u.Name == "" || u.AuthPass == "" || u.PrivPass == "" {
|
|
return fmt.Errorf("simagent: v3 user %q needs a name, auth and priv passphrases", u.Name)
|
|
}
|
|
sp := &gosnmp.UsmSecurityParameters{
|
|
AuthoritativeEngineID: a.engineID, UserName: u.Name,
|
|
AuthenticationProtocol: u.AuthProto, AuthenticationPassphrase: u.AuthPass,
|
|
PrivacyProtocol: u.PrivProto, PrivacyPassphrase: u.PrivPass,
|
|
}
|
|
if err := tbl.Add(u.Name, sp); err != nil {
|
|
return fmt.Errorf("simagent: v3 user %q: %w", u.Name, err)
|
|
}
|
|
}
|
|
a.dec.SetTrapSecurityParametersTable(tbl)
|
|
return nil
|
|
}
|
|
|
|
func engineTime(elapsed time.Duration) uint32 { return uint32(elapsed / time.Second) }
|
|
|
|
func (a *Agent) inTimeWindow(req *gosnmp.SnmpPacket, elapsed time.Duration) bool {
|
|
sp := req.SecurityParameters.(*gosnmp.UsmSecurityParameters)
|
|
d := int64(sp.AuthoritativeEngineTime) - int64(engineTime(elapsed))
|
|
return sp.AuthoritativeEngineBoots == engineBoots && d >= -timeWindow && d <= timeWindow
|
|
}
|
|
|
|
// usmFor returns the request's security parameters (user, keys) restamped
|
|
// with this engine's identity and time.
|
|
func (a *Agent) usmFor(req *gosnmp.SnmpPacket, elapsed time.Duration, salt bool) *gosnmp.UsmSecurityParameters {
|
|
sp := req.SecurityParameters.(*gosnmp.UsmSecurityParameters).Copy().(*gosnmp.UsmSecurityParameters)
|
|
sp.AuthoritativeEngineID = a.engineID
|
|
sp.AuthoritativeEngineBoots = engineBoots
|
|
sp.AuthoritativeEngineTime = engineTime(elapsed)
|
|
sp.AuthenticationParameters = ""
|
|
sp.PrivacyParameters = nil
|
|
if salt {
|
|
sp.PrivacyParameters = binary.BigEndian.AppendUint64(nil, a.salt.Add(1))
|
|
}
|
|
return sp
|
|
}
|
|
|
|
func (a *Agent) report(
|
|
req *gosnmp.SnmpPacket, flags gosnmp.SnmpV3MsgFlags, sp *gosnmp.UsmSecurityParameters, oid string, n uint64,
|
|
) *gosnmp.SnmpPacket {
|
|
return &gosnmp.SnmpPacket{
|
|
Version: gosnmp.Version3, MsgID: req.MsgID, MsgFlags: flags, SecurityModel: gosnmp.UserSecurityModel,
|
|
SecurityParameters: sp, ContextEngineID: a.engineID, ContextName: req.ContextName,
|
|
PDUType: gosnmp.Report, RequestID: req.RequestID,
|
|
Variables: []gosnmp.SnmpPDU{{Name: oid, Type: gosnmp.Counter32, Value: uint32(n)}},
|
|
}
|
|
}
|
|
|
|
// discoveryReport answers an engine-ID discovery probe (RFC 3414 4).
|
|
func (a *Agent) discoveryReport(req *gosnmp.SnmpPacket, elapsed time.Duration) *gosnmp.SnmpPacket {
|
|
sp := &gosnmp.UsmSecurityParameters{
|
|
AuthoritativeEngineID: a.engineID, AuthoritativeEngineBoots: engineBoots, AuthoritativeEngineTime: engineTime(elapsed),
|
|
}
|
|
return a.report(req, gosnmp.NoAuthNoPriv, sp, usmStatsUnknownEngine, a.st.disc.Load())
|
|
}
|
|
|
|
// timeReport is the authenticated notInTimeWindow report that lets the
|
|
// client re-sync its cached engine time.
|
|
func (a *Agent) timeReport(req *gosnmp.SnmpPacket, elapsed time.Duration) *gosnmp.SnmpPacket {
|
|
return a.report(req, gosnmp.AuthNoPriv, a.usmFor(req, elapsed, false), usmStatsNotInTime, a.st.notInTime.Load())
|
|
}
|