Implement ban by software version xep-0092

This commit is contained in:
eleksir
2025-05-29 23:56:19 +03:00
parent 0ed74ed812
commit 3ebd2b48d1
32 changed files with 1793 additions and 7 deletions

View File

@@ -1,6 +1,7 @@
package jabber
import (
"encoding/json"
"fmt"
"regexp"
"strings"
@@ -54,6 +55,21 @@ func (j *Jabber) BunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
}
}
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)
@@ -380,4 +396,208 @@ func (j *Jabber) BunyChat(v xmpp.Chat) error {
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: */

View File

@@ -356,7 +356,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
log.Debugf("Got S2C pong answer from %s to %s", v.From, v.To)
j.ServerPingTimestampRx = time.Now().Unix() //nolint:wsl
// Похоже на понг второй стадии xep-0410 MUC-Ping-а, который у нас не реализован
// Похоже на понг второй стадии xep-0410 MUC-Ping-а, который у нас не реализован.
case v.To == j.Talk.JID() && string(v.Query) == "<XMLElement></XMLElement>":
mucNameMatch := slices.Contains(j.RoomsConnected, v.From)
@@ -425,6 +425,41 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
log.Debug(spew.Sdump(e))
}
// Ответ с результатом адресован нам.
case v.To == j.Talk.JID():
var softwareVersion IqResultSoftwareVersion
if err := xml.Unmarshal(v.Query, &softwareVersion); err == nil {
if softwareVersion.Os == "" {
log.Infof(
"Recieved software version query result for %s: software=%s version=%s",
v.From,
softwareVersion.Name,
softwareVersion.Version,
)
} else {
log.Infof(
"Recieved software version query result for %s: software=%s version=%s os=%s",
v.From,
softwareVersion.Name,
softwareVersion.Version,
softwareVersion.Os,
)
}
if err = j.BunySoftwareVersion(v, softwareVersion); err != nil {
log.Errorf("Unable to query client software version: %s", err)
j.GTomb.Kill(err)
return
}
return
}
log.Info("Got an IQ result. Dunno how deal with it, discarding")
log.Debug(spew.Sdump(e))
default:
log.Info("Got an IQ result. Dunno how deal with it, discarding")
log.Debug(spew.Sdump(e))
@@ -633,7 +668,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
var p xmpp.Presence
_ = json.Unmarshal([]byte(presenceJSONString), &p)
// Если находим, что у нас уже есть клиент с таким же From, то есть полным nick-ом (для grouchat)
// Если находим, что у нас уже есть клиент с таким же From, то есть полным nick-ом (для groupchat)
// просто замещаем его.
if p.From == v.From {
continue

View File

@@ -0,0 +1,36 @@
package jabber
import (
"fmt"
"github.com/eleksir/go-xmpp"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
)
// QuerySoftwareVersion запрошивает версию и название клиента.
func (j *Jabber) QuerySoftwareVersion(jid string) (string, error) {
var (
id string
err error
)
if id, err = j.Talk.RawInformationQuery(
j.Talk.JID(),
jid,
uuid.New().String(),
xmpp.IQTypeGet,
"jabber:iq:version",
"",
); err != nil {
err = fmt.Errorf(
"unable to query software version of jid=%s err=%w",
jid,
err,
)
} else {
log.Infof("Query software version of %s", jid)
}
return id, err
}

View File

@@ -79,6 +79,11 @@ type MyBlackList struct {
JidRe []string `json:"jid_re,omitempty"`
NickRe []string `json:"nick_re,omitempty"`
PhraseRe []string `json:"phrase_re,omitempty"`
UserAgent []struct {
Name string `json:"name,omitempty"`
Version string `json:"version,omitempty"`
Os string `json:"os,omitempty"`
} `json:"user_agent,omitempty"`
} `json:"blacklist,omitempty"`
}
@@ -197,4 +202,14 @@ type IqErrorCancelNotAcceptable struct {
} `xml:"not-acceptable"`
}
// IqResultSoftwareVersion прототип структурки для разбора IQ ответов на запрос о названии и версии клиентского ПО.
type IqResultSoftwareVersion struct {
XMLName xml.Name `xml:"query"`
Text string `xml:",chardata"`
Xmlns string `xml:"xmlns,attr"`
Name string `xml:"name"`
Version string `xml:"version"`
Os string `xml:"os,omitempty"`
}
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */