package redis import ( "bufio" "strconv" "strings" osm "git.loafle.net/overflow/service_matcher-go" ) const REDIS_PING string = "*1\r\n$4\r\nPING\r\n" const REDIS_INFO string = "*1\r\n$4\r\nINFO\r\n" const REDIS_QUIT string = "*1\r\n$4\r\nQUIT\r\n" type RedisMatcher struct { osm.Matchers } func (m *RedisMatcher) Key(matchCtx *osm.MatchCtx) string { return "REDIS" } func (m *RedisMatcher) Type(matchCtx *osm.MatchCtx) string { return "NOSQL" } func (m *RedisMatcher) Vendor(matchCtx *osm.MatchCtx) string { return "Redis" } func (m *RedisMatcher) Version(matchCtx *osm.MatchCtx) string { v := "UNKNOWN" if _v, ok := matchCtx.GetAttribute("redis_version"); ok { v = _v } return v } func (m *RedisMatcher) OsType(matchCtx *osm.MatchCtx) string { return "UNKNOWN" } func (m *RedisMatcher) OsVersion(matchCtx *osm.MatchCtx) string { return "UNKNOWN" } func (m *RedisMatcher) Name(matchCtx *osm.MatchCtx) string { name := "Redis" if v, ok := matchCtx.GetAttribute("protected"); ok { if _v, _err := strconv.ParseBool(v); nil != _err && _v { return name + " (protected)" } } if v, ok := matchCtx.GetAttribute("redis_mode"); ok { name = name + " " + v } return name } func (m *RedisMatcher) IsPrePacket() bool { return false } func (m *RedisMatcher) HasResponse(matchCtx *osm.MatchCtx, index int) bool { return true } func (m *RedisMatcher) IsError(matchCtx *osm.MatchCtx, index int, packet *osm.Packet) bool { return false } func (m *RedisMatcher) Match(matchCtx *osm.MatchCtx, index int, packet *osm.Packet) error { if packet == nil || !packet.Valid() { return osm.NoPacketReceivedError() } resp := strings.Split(string(packet.Bytes()), "\r\n")[0] if len(resp) <= 0 { return osm.NotMatchedError() } switch index { case 0: sign := string([]rune(resp)[0]) if len(sign) <= 0 { return osm.NotMatchedError() } if sign == "+" { if resp == "+PONG" || resp == "+OK" { return nil } } if sign == "-" { if resp == "-NOAUTH" || resp == "-ERR" { return nil } } protected := m.checkProtectedMode(packet) matchCtx.SetAttribute("protected", strconv.FormatBool(protected)) if protected { return nil } case 1: // INFO response := string(packet.Bytes()) m.parseResponse(matchCtx, response) if v, ok := matchCtx.GetAttribute("protected"); ok { if _v, _err := strconv.ParseBool(v); nil == _err && _v { m.parseResponse(matchCtx, response) } } return nil case 2: sign := string([]rune(resp)[0]) if sign == "+" || sign == "-" { return nil } return nil default: return osm.NotMatchedError() } return osm.NotMatchedError() } func (m *RedisMatcher) checkProtectedMode(packet *osm.Packet) bool { var ( compareSign = "-" compareMsg = "DENIED" ) str := string(packet.Bytes()) if str == "" { return false } if 0 >= len(str) || len(compareMsg)+2 > len(str) { return false } firstCompare := str[0:1] seconcdCompare := str[1 : len(compareMsg)+1] if firstCompare != compareSign { return false } if seconcdCompare != compareMsg { return false } return true } func (m *RedisMatcher) parseResponse(matchCtx *osm.MatchCtx, response string) { scanner := bufio.NewScanner(strings.NewReader(response)) for scanner.Scan() { line := scanner.Text() // log.Println(line) if strings.Compare(line, "") == 0 { break } if len(line) > 0 && strings.Contains(line, ":") { kv := strings.Split(line, ":") if len(kv[0]) > 0 && len(kv[1]) > 0 { matchCtx.SetAttribute(kv[0], kv[1]) } } } } func (m *RedisMatcher) PacketCount(matchCtx *osm.MatchCtx) int { if v, ok := matchCtx.GetAttribute("protected"); ok { if _v, _err := strconv.ParseBool(v); nil != _err && _v { return 1 } } return 3 } func NewMatcher() osm.Matcher { m := &RedisMatcher{} m.AddPacket(osm.NewPacket([]byte(REDIS_PING), len(REDIS_PING))) m.AddPacket(osm.NewPacket([]byte(REDIS_INFO), len(REDIS_INFO))) m.AddPacket(osm.NewPacket([]byte(REDIS_QUIT), len(REDIS_QUIT))) return m }