package pkg import ( "fmt" "regexp" "strings" ) type ( SNMPConfig struct { AgentAddresses []string AllowedAddresses []string `json:"allowed_addresses"` AuthTrapEnabled bool `json:"auth_trap_enabled"` Communities []SNMPCommunity `json:"communities"` DiskMonitors []SNMPDiskMonitor `json:"disk_monitors"` ProcessMonitors []SNMPProcessMonitor `json:"process_monitors"` TrapsV2 []SNMPTrapV2 `json:"traps_v2"` TrapsV3 []SNMPTrapV3 `json:"traps_v3"` Users []SNMPUser `json:"users"` UnknownConfig []string `json:"unknown_config"` RawConfigTxt string `json:"raw_config_txt"` } SNMPCommunity struct { Id string `json:"id"` CommunityName string `json:"community-name"` Source string `json:"source"` OidSubset string `json:"oid_subset"` Access string `json:"access"` Description string `json:"description"` } SNMPDiskMonitor struct { Id string `json:"id"` MinSpace string `json:"min_space"` Path string `json:"path"` } SNMPProcessMonitor struct { Id string `json:"id"` Process string `json:"process"` MaxProcesses string `json:"max_processes"` } SNMPTrapV2 struct { Id string `json:"id"` Community string `json:"community"` Host string `json:"host"` } SNMPTrapV3 struct { Id string `json:"id"` Host string `json:"host"` SecurityName string `json:"security_name"` SecurityLevel string `json:"security_level"` AuthProtocol string `json:"auth_protocol"` AuthPassword string `json:"auth_password"` PrivProtocol string `json:"priv_protocol"` PrivPassword string `json:"priv_password"` } SNMPUser struct { Id string `json:"id"` UserName string `json:"user_name"` SecurityLevel string `json:"security_level"` AuthProtocol string `json:"auth_protocol"` AuthPassword string `json:"auth_password"` AuthPasswordEncrypted string `json:"auth_password_encrypted"` PrivProtocol string `json:"priv_protocol"` PrivPassword string `json:"priv_password"` PrivPasswordEncrypted string `json:"priv_password_encrypted"` Access string `json:"access"` Description string `json:"description"` OidSubset string `json:"oid_subset"` } ) var ( rgxTrapHost = regexp.MustCompile(`(?i)host\s+(\S+)`) ) func (config *SNMPConfig) Parse(raw string) error { config.RawConfigTxt = raw splitLines := strings.Split(raw, "\n") //clear out existing config to make sure we don't have stale data config.AgentAddresses = []string{} config.AllowedAddresses = []string{} config.AuthTrapEnabled = false config.Communities = []SNMPCommunity{} config.DiskMonitors = []SNMPDiskMonitor{} config.ProcessMonitors = []SNMPProcessMonitor{} config.TrapsV2 = []SNMPTrapV2{} config.TrapsV3 = []SNMPTrapV3{} config.Users = []SNMPUser{} //set up some booleans to skip regexing for data we already know we have inSysSnmp := false inSubData := false agentAddressesFound := false allowedAddressesFound := false authTrapEnabledFound := false communitiesFound := false inCommunities := false currentCommunity := "" diskMonitorsFound := false inDiskMonitors := false currentDiskMonitor := "" processMonitorsFound := false inProcessMonitors := false currentProcessMonitor := "" trapsFound := false var trapLines []string inTraps := false currentTrap := "" usersFound := false inUsers := false currentUser := "" inUnknown := false currentUnknown := "" //loop through our raw config lines and do magicks for _, line := range splitLines { //make sure we're in our 'sys snmp' block if !inSysSnmp && !inSubData { if regexp.MustCompile(`(?i)sys snmp\s+{`).MatchString(line) { inSysSnmp = true continue } } if !inSysSnmp { continue } //Agent Addresses if !agentAddressesFound && !inSubData { matchAgentAddresses := regexp.MustCompile(`(?i)agent-addresses { ?(.*) }`).FindStringSubmatch(line) if len(matchAgentAddresses) == 2 { agentAddressesFound = true for _, agent := range strings.Split(matchAgentAddresses[1], " ") { config.AgentAddresses = append(config.AgentAddresses, agent) } continue } } //Allowed Addresses if !allowedAddressesFound && !inSubData { matchAllowedAddresses := regexp.MustCompile(`(?i)allowed-addresses { ?(.*) }`).FindStringSubmatch(line) if len(matchAllowedAddresses) == 2 { allowedAddressesFound = true for _, allowed := range strings.Split(matchAllowedAddresses[1], " ") { config.AllowedAddresses = append(config.AllowedAddresses, allowed) } continue } } //Auth-Trap if !authTrapEnabledFound && !inSubData { matchAuthTrapEnabled := regexp.MustCompile(`(?i)auth-trap (\S+)`).FindStringSubmatch(line) if len(matchAuthTrapEnabled) == 2 { authTrapEnabledFound = true if matchAuthTrapEnabled[1] == "enabled" { config.AuthTrapEnabled = true } } } //Communities (SNMP v2) if !communitiesFound && !inSubData { if regexp.MustCompile(`(?i)communities\s+{`).MatchString(line) { inSubData = true inCommunities = true continue } } if inCommunities { if currentCommunity == "" { //find and open a community id matchCommunityId := regexp.MustCompile(`(?i)(\S+)\s+{`).FindStringSubmatch(line) if len(matchCommunityId) == 2 { var community SNMPCommunity community.Id = matchCommunityId[1] currentCommunity = community.Id config.Communities = append(config.Communities, community) continue } //or close out our communities block if strings.TrimSpace(line) == "}" { communitiesFound = true inCommunities = false inSubData = false continue } } else { //if currentCommunity != "" we are in a specific community and need to populate stuff matchCommunityName := regexp.MustCompile(`(?i)community-name\s+(\S+)`).FindStringSubmatch(line) if len(matchCommunityName) == 2 { for i, c := range config.Communities { if c.Id == currentCommunity { config.Communities[i].CommunityName = matchCommunityName[1] break } } continue } matchCommunityOid := regexp.MustCompile(`(?i)oid-subset\s+(\S+)`).FindStringSubmatch(line) if len(matchCommunityOid) == 2 { for i, c := range config.Communities { if c.Id == currentCommunity { config.Communities[i].OidSubset = matchCommunityOid[1] break } } continue } matchCommunitySource := regexp.MustCompile(`(?i)source\s+(\S+)`).FindStringSubmatch(line) if len(matchCommunitySource) == 2 { for i, c := range config.Communities { if c.Id == currentCommunity { config.Communities[i].Source = matchCommunitySource[1] break } } continue } matchCommunityAccess := regexp.MustCompile(`(?i)access\s+(\S+)`).FindStringSubmatch(line) if len(matchCommunityAccess) == 2 { for i, c := range config.Communities { if c.Id == currentCommunity { config.Communities[i].Access = matchCommunityAccess[1] break } } continue } matchCommunityDescription := regexp.MustCompile(`(?i)description\s+(\S+.*)`).FindStringSubmatch(line) if len(matchCommunityDescription) == 2 { for i, c := range config.Communities { if c.Id == currentCommunity { config.Communities[i].Description = matchCommunityDescription[1] break } } continue } if strings.TrimSpace(line) == "}" { currentCommunity = "" continue } } } //Disk Monitors if !diskMonitorsFound && !inSubData { if regexp.MustCompile(`(?i)disk-monitors\s+{`).MatchString(line) { inSubData = true inDiskMonitors = true continue } } if inDiskMonitors { if currentDiskMonitor == "" { //find and open a diskmon id matchDiskMonId := regexp.MustCompile(`(?i)(\S+)\s+{`).FindStringSubmatch(line) if len(matchDiskMonId) == 2 { var diskmon SNMPDiskMonitor diskmon.Id = matchDiskMonId[1] currentDiskMonitor = diskmon.Id config.DiskMonitors = append(config.DiskMonitors, diskmon) continue } //or close our diskmonitors block if strings.TrimSpace(line) == "}" { diskMonitorsFound = true inDiskMonitors = false inSubData = false continue } } else { //if currentDiskMonitor != "" we are in a specific monitor and need to populate stuff matchDiskMonMinspace := regexp.MustCompile(`(?i)minspace\s+(\S+)`).FindStringSubmatch(line) if len(matchDiskMonMinspace) == 2 { for i, d := range config.DiskMonitors { if d.Id == currentDiskMonitor { config.DiskMonitors[i].MinSpace = matchDiskMonMinspace[1] break } } continue } matchDiskMonPath := regexp.MustCompile(`(?i)path\s+(\S+)`).FindStringSubmatch(line) if len(matchDiskMonPath) == 2 { for i, d := range config.DiskMonitors { if d.Id == currentDiskMonitor { config.DiskMonitors[i].Path = matchDiskMonPath[1] break } } continue } if strings.TrimSpace(line) == "}" { currentDiskMonitor = "" continue } } } //ProcessMonitors if !processMonitorsFound && !inSubData { if regexp.MustCompile(`(?i)process-monitors\s+{`).MatchString(line) { inSubData = true inProcessMonitors = true continue } } if inProcessMonitors { if currentProcessMonitor == "" { //find and open a processmonitor id matchProcessMonId := regexp.MustCompile(`(?i)(\S+)\s+{`).FindStringSubmatch(line) if len(matchProcessMonId) == 2 { var procmon SNMPProcessMonitor procmon.Id = matchProcessMonId[1] currentProcessMonitor = procmon.Id config.ProcessMonitors = append(config.ProcessMonitors, procmon) continue } //or close out our processmonitors block if strings.TrimSpace(line) == "}" { processMonitorsFound = true inProcessMonitors = false inSubData = false continue } } else { //if currentProcessMonitor != "" we are in a specific monitor and need to populate stuff matchProcMonProcess := regexp.MustCompile(`(?i)process\s+(\S+)`).FindStringSubmatch(line) if len(matchProcMonProcess) == 2 { for i, p := range config.ProcessMonitors { if p.Id == currentProcessMonitor { config.ProcessMonitors[i].Process = matchProcMonProcess[1] break } } continue } matchProcMonMaxProcesses := regexp.MustCompile(`(?i)max-processes\s+(\S+)`).FindStringSubmatch(line) if len(matchProcMonMaxProcesses) == 2 { for i, p := range config.ProcessMonitors { if p.Id == currentProcessMonitor { config.ProcessMonitors[i].MaxProcesses = matchProcMonMaxProcesses[1] break } } continue } if strings.TrimSpace(line) == "}" { currentProcessMonitor = "" continue } } } //Traps // Traps are version dependent, so we'll send the lines to a function to find version first if !trapsFound && !inSubData { if regexp.MustCompile(`(?i)traps\s+{`).MatchString(line) { inSubData = true inTraps = true continue } } if inTraps { if currentTrap == "" { //find and open a trap id matchTrapId := regexp.MustCompile(`(?i)(\S+)\s+{`).FindStringSubmatch(line) if len(matchTrapId) == 2 { currentTrap = matchTrapId[1] currentTrap = matchTrapId[1] continue } //or close out our traps block if strings.TrimSpace(line) == "}" { trapsFound = true inTraps = false inSubData = false continue } } else { //if currentTrap != "" we're in a specific trap and need to send those lines to our parsetrap if strings.TrimSpace(line) == "}" { config.parseTrap(currentTrap, trapLines) currentTrap = "" trapLines = []string{} continue } else { trapLines = append(trapLines, line) continue } } } //Users (SNMP v3) if !usersFound && !inSubData { if regexp.MustCompile(`(?i)users\s+{`).MatchString(line) { inSubData = true inUsers = true continue } } if inUsers { if currentUser == "" { //find and open a user id matchUserId := regexp.MustCompile(`(?i)(\S+)\s+{`).FindStringSubmatch(line) if len(matchUserId) == 2 { var snmpUser SNMPUser snmpUser.Id = matchUserId[1] currentUser = snmpUser.Id config.Users = append(config.Users, snmpUser) continue } //or close out our users block if strings.TrimSpace(line) == "}" { usersFound = true inUsers = false inSubData = false continue } } else { //if currentUser != "" we are in a specific user and need to populate stuff matchUserName := regexp.MustCompile(`(?i)username\s+(\S+)`).FindStringSubmatch(line) if len(matchUserName) == 2 { for i, u := range config.Users { if u.Id == currentUser { config.Users[i].UserName = matchUserName[1] break } } continue } matchUserSecLevel := regexp.MustCompile(`(?i)security-level\s+(\S+)`).FindStringSubmatch(line) if len(matchUserSecLevel) == 2 { for i, u := range config.Users { if u.Id == currentUser { config.Users[i].SecurityLevel = matchUserSecLevel[1] break } } continue } matchUserAuthProtocol := regexp.MustCompile(`(?i)auth-protocol\s+(\S+)`).FindStringSubmatch(line) if len(matchUserAuthProtocol) == 2 { for i, u := range config.Users { if u.Id == currentUser { config.Users[i].AuthProtocol = matchUserAuthProtocol[1] break } } continue } matchUserAuthPass := regexp.MustCompile(`(?i)auth-password\s+(\S+)`).FindStringSubmatch(line) if len(matchUserAuthPass) == 2 { for i, u := range config.Users { if u.Id == currentUser { config.Users[i].AuthPassword = matchUserAuthPass[1] break } } continue } matchUserAuthPassEncrypted := regexp.MustCompile(`(?i)auth-password-encrypted\s+(\S+)`).FindStringSubmatch(line) if len(matchUserAuthPassEncrypted) == 2 { for i, u := range config.Users { if u.Id == currentUser { config.Users[i].AuthPasswordEncrypted = strings.TrimRight(strings.TrimLeft(matchUserAuthPassEncrypted[1], "\""), "\"") break } } continue } matchUserPrivProtocol := regexp.MustCompile(`(?i)privacy-protocol\s+(\S+)`).FindStringSubmatch(line) if len(matchUserPrivProtocol) == 2 { for i, u := range config.Users { if u.Id == currentUser { config.Users[i].PrivProtocol = matchUserPrivProtocol[1] break } } continue } matchUserPrivPass := regexp.MustCompile(`(?i)privacy-password\s+(\S+)`).FindStringSubmatch(line) if len(matchUserPrivPass) == 2 { for i, u := range config.Users { if u.Id == currentUser { config.Users[i].PrivPassword = matchUserPrivPass[1] break } } continue } matchUserPrivPassEncrypted := regexp.MustCompile(`(?i)privacy-password-encrypted\s+(\S+)`).FindStringSubmatch(line) if len(matchUserPrivPassEncrypted) == 2 { for i, u := range config.Users { if u.Id == currentUser { config.Users[i].PrivPasswordEncrypted = strings.TrimRight(strings.TrimLeft(matchUserPrivPassEncrypted[1], "\""), "\"") break } } continue } matchUserAccess := regexp.MustCompile(`(?i)access\s+(\S+)`).FindStringSubmatch(line) if len(matchUserAccess) == 2 { for i, u := range config.Users { if u.Id == currentUser { config.Users[i].Access = matchUserAccess[1] break } } continue } matchUserDescription := regexp.MustCompile(`(?i)description\s+(\S+.*)`).FindStringSubmatch(line) if len(matchUserDescription) == 2 { for i, u := range config.Users { if u.Id == currentUser { config.Users[i].Description = strings.TrimRight(strings.TrimLeft(matchUserDescription[1], "\""), "\"") break } } continue } matchUserOidSubset := regexp.MustCompile(`(?i)oid-subset\s+(\S+)`).FindStringSubmatch(line) if len(matchUserOidSubset) == 2 { for i, u := range config.Users { if u.Id == currentUser { config.Users[i].OidSubset = matchUserOidSubset[1] break } } continue } if strings.TrimSpace(line) == "}" { currentUser = "" continue } } } //Handle unexpected properties (like Users but unexpected) // This should be at the VERY END of other matches, and make sure continues are used above if !inUnknown && !inSubData { if regexp.MustCompile(`(?i)\S+\s+{`).MatchString(line) { config.UnknownConfig = append(config.UnknownConfig, line) inSubData = true inUnknown = true continue } } if inUnknown { config.UnknownConfig = append(config.UnknownConfig, line) if currentUnknown == "" { //find and open an unknown property matchUnknownId := regexp.MustCompile(`(?i)(\S+)\s+{`).FindStringSubmatch(line) if len(matchUnknownId) == 2 { currentUnknown = matchUnknownId[1] continue } //or close out our unknown block if strings.TrimSpace(line) == "}" { inUnknown = false inSubData = false continue } } else { if strings.TrimSpace(line) == "}" { currentUnknown = "" continue } } } //close out our last } for 'sys snmp' if !inSubData { if strings.TrimSpace(line) == "}" { inSysSnmp = false break } } } //make sure we closed out all of our blocks appropriately blocksStillOpen := "" switch { case inSubData: blocksStillOpen += "inSubData, " case inCommunities: blocksStillOpen += "inCommunities, " case inDiskMonitors: blocksStillOpen += "inDiskMonitors, " case inProcessMonitors: blocksStillOpen += "inProcessMonitors, " case inTraps: blocksStillOpen += "inTraps, " case inUsers: blocksStillOpen += "inUsers, " case inUnknown: blocksStillOpen += "inUnknown, " } if blocksStillOpen != "" { return fmt.Errorf("Error, blocks did not close out properly: %s\nRaw String: %s\n", strings.TrimRight(blocksStillOpen, ", "), raw) } return nil } func (config *SNMPConfig) parseTrap(id string, lines []string) { v3Trap := false for _, l := range lines { matchTrapVersion := regexp.MustCompile(`(?i)version\s+(\S+)`).FindStringSubmatch(l) if len(matchTrapVersion) == 2 { if matchTrapVersion[1] == "3" { v3Trap = true } break } } if !v3Trap { //parse host and community into a v2Trap var trapV2 SNMPTrapV2 trapV2.Id = id for _, l := range lines { matchTrapHost := rgxTrapHost.FindStringSubmatch(l) if len(matchTrapHost) == 2 { trapV2.Host = matchTrapHost[1] continue } matchTrapCommunity := regexp.MustCompile(`(?i)community\s+(\S+)`).FindStringSubmatch(l) if len(matchTrapCommunity) == 2 { trapV2.Community = matchTrapCommunity[1] continue } } config.TrapsV2 = append(config.TrapsV2, trapV2) return } //parse host and v3 crap into a v3Trap var trapV3 SNMPTrapV3 trapV3.Id = id for _, l := range lines { matchTrapHost := rgxTrapHost.FindStringSubmatch(l) if len(matchTrapHost) == 2 { trapV3.Host = matchTrapHost[1] continue } matchTrapSecName := regexp.MustCompile(`(?i)security-name\s+(\S+)`).FindStringSubmatch(l) if len(matchTrapSecName) == 2 { trapV3.SecurityName = matchTrapSecName[1] continue } matchTrapSecLevel := regexp.MustCompile(`(?i)security-level\s+(\S+)`).FindStringSubmatch(l) if len(matchTrapSecLevel) == 2 { trapV3.SecurityLevel = matchTrapSecLevel[1] continue } matchTrapAuthProtocol := regexp.MustCompile(`(?i)auth-protocol\s+(\S+)`).FindStringSubmatch(l) if len(matchTrapAuthProtocol) == 2 { trapV3.AuthProtocol = matchTrapAuthProtocol[1] continue } matchTrapAuthPass := regexp.MustCompile(`(?i)auth-password-encrypted\s+(\S+)`).FindStringSubmatch(l) if len(matchTrapAuthPass) == 2 { trapV3.AuthPassword = matchTrapAuthPass[1] continue } matchTrapPrivProtocol := regexp.MustCompile(`(?i)privacy-protocol\s+(\S+)`).FindStringSubmatch(l) if len(matchTrapPrivProtocol) == 2 { trapV3.PrivProtocol = matchTrapPrivProtocol[1] continue } matchTrapPrivPass := regexp.MustCompile(`(?i)privacy-password-encrypted\s+(\S+)`).FindStringSubmatch(l) if len(matchTrapPrivPass) == 2 { trapV3.PrivPassword = matchTrapPrivPass[1] continue } } config.TrapsV3 = append(config.TrapsV3, trapV3) return }