package jabber import ( "encoding/json" "fmt" "regexp" "slices" "strings" "github.com/davecgh/go-spew/spew" "github.com/eleksir/go-xmpp" log "github.com/sirupsen/logrus" ) // BunyPresense производит проверку по бело-чёрным спискам. Если presence пришёл от злодея (из чёрного списка), то // отправляет его в бан. func (j *Jabber) BunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo var err error // Если у presence-а есть JID и presence из одной из комнат, в которой мы есть и если его домен в чёрном // списке, заносим пидора в список outcast-ов. if v.JID != "" { // На всякий случай: себя никогда не баним, явным образом. if v.JID == j.Talk.JID() { return err } room := strings.SplitN(v.From, "/", 2)[0] if room == "" { log.Infof("We got empty room field in presence event, which kinda strange: %s", spew.Sdump(v)) return err } goodJid := strings.SplitN(v.JID, "/", 2)[0] // Обрабатываем правила белого списка. for _, good := range j.WhiteList.Whitelist { // Глобальный белый список. if good.RoomName == "" { if slices.Contains(good.Jid, goodJid) { return err } } // Белый список для конкретных каналов. if good.RoomName == room { if slices.Contains(good.Jid, goodJid) { return err } } } id, err := j.QuerySoftwareVersion(v.From) if err != nil { err := fmt.Errorf( "unable to query user software and version: jid=%s id=%s, err=%w", v.JID, id, err, ) j.GTomb.Kill(err) return err } evilJid := strings.SplitN(v.JID, "/", 2)[0] evilNick := "" evilNicks := strings.SplitN(v.From, "/", 2) // Давайте-ка похэндлим нонсенс - когда у нас в строке нету разделителя. if len(evilNicks) > 1 { evilNick = evilNicks[1] } // Обрабатываем правила чёрного списка. for _, cRoom := range j.RoomsConnected { if cRoom == room { for _, bEntry := range j.BlackList.Blacklist { // Обработаем правила глобального чёрного списка. if bEntry.RoomName == "" { for _, jidRegexp := range bEntry.JidRe { if jidRegexp == "" { continue } re, err := regexp.Compile(jidRegexp) if err != nil { log.Errorf("Incorrect regexp in global blacklist: %s, skipping", jidRegexp) continue } log.Debugf("Checking jid %s vs global blacklist regex %s", v.JID, jidRegexp) if re.MatchString(v.JID) { log.Warnf( "Hammer falls on %s (%s): jid matches with global blacklist entry: %s", v.From, evilJid, jidRegexp, ) id, err := j.Squash(room, evilJid, bEntry.ReasonEnable, v.Type) if err != nil { err := fmt.Errorf( "unable to ban user: id=%s, err=%w", id, err, ) j.GTomb.Kill(err) } return err } } for _, nickRegexp := range bEntry.NickRe { if nickRegexp == "" { continue } re, err := regexp.Compile(nickRegexp) if err != nil { log.Errorf("Incorrect regexp in global blacklist: %s, skipping", nickRegexp) continue } log.Debugf("Checking nick %s vs global blacklist regex %s", evilNick, nickRegexp) if re.MatchString(evilNick) { log.Warnf( "Hammer falls on %s (%s): nick matches with global blacklist entry: %s", v.From, evilJid, nickRegexp, ) // Баним именно jid. id, err := j.Squash(room, evilJid, bEntry.ReasonEnable, v.Type) if err != nil { err := fmt.Errorf( "unable to ban user: id=%s, err=%w", id, err, ) j.GTomb.Kill(err) } return err } } continue } // Обработаем правила конкретного канала, комнаты конференции. if bEntry.RoomName == room { for _, jidRegexp := range bEntry.JidRe { if jidRegexp == "" { continue } re, err := regexp.Compile(jidRegexp) if err != nil { log.Errorf("Incorrect regexp in room %s blacklist: %s, skipping", room, jidRegexp) continue } log.Debugf("Checking jid %s vs room %s blacklist regex %s", v.JID, room, jidRegexp) if re.MatchString(v.JID) { log.Warnf( "Hammer falls on %s (%s): jid matches with room blacklist entry: %s", v.From, evilJid, jidRegexp, ) id, err := j.Squash(room, evilJid, bEntry.ReasonEnable, v.Type) if err != nil { err := fmt.Errorf( "unable to ban user: id=%s, err=%w", id, err, ) j.GTomb.Kill(err) } return err } } if bEntry.RoomName == room { for _, nickRegexp := range bEntry.NickRe { if nickRegexp == "" { continue } re, err := regexp.Compile(nickRegexp) if err != nil { log.Errorf("Incorrect regexp in room %s blacklist: %s, skipping", room, nickRegexp) continue } log.Debugf("Checking nick %s vs room %s blacklist regex %s", evilJid, room, nickRegexp) if re.MatchString(evilNick) { log.Warnf( "Hammer falls on %s (%s): nick matches with room blacklist entry: %s", v.From, evilJid, nickRegexp, ) // Баним именно jid. id, err := j.Squash(room, evilJid, bEntry.ReasonEnable, v.Type) if err != nil { err := fmt.Errorf( "unable to ban user: id=%s, err=%w", id, err, ) j.GTomb.Kill(err) } return err } } } continue } } } } } return err } // BunyChat производит проверку сообщений участников чата по списку забаненных фраз и в случае нахождения запрещённого // шаблона банит участника чата. func (j *Jabber) BunyChat(v xmpp.Chat) error { var ( room = (strings.SplitN(v.Remote, "/", 2))[0] err error ) // Действовать мы можем только в рамках тех комнат, где явно присуствуем. for _, cRoom := range j.RoomsConnected { if cRoom == room { // Перебирём правила чёрных списков. for _, bEntry := range j.BlackList.Blacklist { // Обработаем правила глобального чёрного списка. if bEntry.RoomName == "" { for _, phraseRegexp := range bEntry.PhraseRe { if phraseRegexp == "" { continue } re, err := regexp.Compile(phraseRegexp) if err != nil { log.Errorf("Incorrect regexp in room %s blacklist: %s, skipping", room, phraseRegexp) continue } log.Debugf("Checking phrase %s vs room %s blacklist regex %s", v.Text, room, phraseRegexp) if re.MatchString(v.Text) { realJID := j.GetRealJIDfromNick(v.Remote) log.Warnf( "Hammer falls on %s (%s): phrase matches with global blacklist entry: %s vs %s", v.Remote, realJID, v.Text, phraseRegexp, ) if id, err := j.Squash(room, realJID, bEntry.ReasonEnable, v.Type); err != nil { err := fmt.Errorf( "unable to ban user: id=%s, err=%w", id, err, ) j.GTomb.Kill(err) } return err } } } // Обработаем правила чёрного списка для конкретной комнаты. if bEntry.RoomName == room { for _, phraseRegexp := range bEntry.PhraseRe { if phraseRegexp == "" { continue } re, err := regexp.Compile(phraseRegexp) if err != nil { log.Errorf("Incorrect regexp in room %s blacklist: %s, skipping", room, phraseRegexp) continue } log.Debugf("Checking phrase %s vs room %s blacklist regex %s", v.Text, room, phraseRegexp) if re.MatchString(v.Text) { realJID := j.GetRealJIDfromNick(v.Remote) log.Warnf( "Hammer falls on %s (%s): phrase matches with room blacklist entry: %s vs %s", v.Remote, realJID, v.Text, phraseRegexp, ) if id, err := j.Squash(room, realJID, bEntry.ReasonEnable, v.Type); err != nil { err := fmt.Errorf( "unable to ban user: id=%s, err=%w", id, err, ) j.GTomb.Kill(err) } return err } } } } // Если включено, проверяем фразу на КАПС. for _, channel := range j.C.Jabber.Channels { if channel.Name == room && channel.AllCaps.Enabled { // Нормализуем строку и вырежем из неё пробелы. normPhrase := strings.ReplaceAll(nString(v.Text), " ", "") // Проверяем согласно тому, что длина фразы более чем сколько-то символов. if len(normPhrase) >= channel.AllCaps.MinLength { normPhraseUpper := strings.ReplaceAll(nStringUpper(v.Text), " ", "") if normPhrase == normPhraseUpper { realJID := j.GetRealJIDfromNick(v.Remote) id, err := j.Squash(room, realJID, false, v.Type) if err != nil { err := fmt.Errorf( "unable to ban user: id=%s, err=%w", id, err, ) j.GTomb.Kill(err) } } } } } break } } return err } // BunySoftwareVersion производит проверку по чёрному списку версий и названий клинтского ПО. func (j *Jabber) BunySoftwareVersion(v xmpp.IQ, ver IqResultSoftwareVersion) error { var err error if v.From != "" { // На всякий случай: себя никогда не баним, явным образом. if v.From == j.Talk.JID() { return err } room := strings.SplitN(v.From, "/", 2)[0] var ( presenceJSONStrings []string ) if presenceJSONInterface, present := j.RoomPresences.Get(room); present { presenceJSONStrings = InterfaceToStringSlice(presenceJSONInterface) } log.Debugf("Looking up %s in presence db", v.From) for _, presenceJSONString := range presenceJSONStrings { var p xmpp.Presence _ = json.Unmarshal([]byte(presenceJSONString), &p) if p.Affiliation == "admin" || p.Affiliation == "owner" { log.Infof("Skipping useragent blacklist check for %s because his affiliation is %s", v.From, p.Affiliation) continue } // На всякий случай. if p.Role == "moderator" { log.Infof("Skipping useragent blacklist check for %s because his role is %s", v.From, p.Role) continue } if v.From == p.From { log.Errorf("Found jid of %s in presence db: %s", v.From, p.JID) // Lookup in global blacklist. for _, cRoom := range j.RoomsConnected { if cRoom == room { for _, bEntry := range j.BlackList.Blacklist { // Обработаем правила глобального чёрного списка. if bEntry.RoomName == "" { for _, useragent := range bEntry.UserAgent { switch { case useragent.Name != "" && useragent.Version != "" && useragent.Os != "": if ver.Name == useragent.Name && ver.Version == useragent.Version && ver.Os == useragent.Os { id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type) if err != nil { err := fmt.Errorf( "unable to ban user: id=%s, err=%w", id, err, ) j.GTomb.Kill(err) } return err } case useragent.Name != "" && useragent.Version != "": if ver.Name == useragent.Name && ver.Version == useragent.Version { id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type) if err != nil { err := fmt.Errorf( "unable to ban user: id=%s, err=%w", id, err, ) j.GTomb.Kill(err) } return err } case useragent.Name != "": if ver.Name == useragent.Name { id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type) if err != nil { err := fmt.Errorf( "unable to ban user: id=%s, err=%w", id, err, ) j.GTomb.Kill(err) } return err } case useragent.Version != "": if ver.Version == useragent.Version { id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type) if err != nil { err := fmt.Errorf( "unable to ban user: id=%s, err=%w", id, err, ) j.GTomb.Kill(err) } return err } } } } if bEntry.RoomName == room { for _, useragent := range bEntry.UserAgent { switch { case useragent.Name != "" && useragent.Version != "" && useragent.Os != "": if ver.Name == useragent.Name && ver.Version == useragent.Version && ver.Os == useragent.Os { id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type) if err != nil { err := fmt.Errorf( "unable to ban user: id=%s, err=%w", id, err, ) j.GTomb.Kill(err) } return err } case useragent.Name != "" && useragent.Version != "": if ver.Name == useragent.Name && ver.Version == useragent.Version { id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type) if err != nil { err := fmt.Errorf( "unable to ban user: id=%s, err=%w", id, err, ) j.GTomb.Kill(err) } return err } case useragent.Name != "": if ver.Name == useragent.Name { id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type) if err != nil { err := fmt.Errorf( "unable to ban user: id=%s, err=%w", id, err, ) j.GTomb.Kill(err) } return err } case useragent.Version != "": if ver.Version == useragent.Version { id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type) if err != nil { err := fmt.Errorf( "unable to ban user: id=%s, err=%w", id, err, ) j.GTomb.Kill(err) } return err } } } } } } } } } } return err } /* vim: set ft=go noet ai ts=4 sw=4 sts=4: */