]> git.r.bdr.sh - rbdr/mobius/commitdiff
Implement user ban
authorJeff Halter <redacted>
Fri, 24 Jun 2022 23:10:42 +0000 (16:10 -0700)
committerJeff Halter <redacted>
Fri, 24 Jun 2022 23:10:42 +0000 (16:10 -0700)
hotline/ban.go [new file with mode: 0644]
hotline/field.go
hotline/server.go
hotline/transaction_handlers.go

diff --git a/hotline/ban.go b/hotline/ban.go
new file mode 100644 (file)
index 0000000..0b75e8e
--- /dev/null
@@ -0,0 +1,5 @@
+package hotline
+
+import "time"
+
+const tempBanDuration = 30 * time.Minute
index aef790d171351e3c78ce8e362a01166e4a3beb74..30760170b4710bacc734068319a2894a73568d5e 100644 (file)
@@ -19,7 +19,7 @@ const fieldUserAccess = 110
 
 // const fieldUserAlias = 111 TODO: implement
 const fieldUserFlags = 112
-const fieldOptions = 113
+const fieldOptions = 113 // Server message (1) or admin message (any other value)
 const fieldChatID = 114
 const fieldChatSubject = 115
 const fieldWaitingCount = 116
index 1edfd4a9d03a65224bd66a52c2b5dba185621780..ec605276fe255e7a060f7ee3034299033d36b88c 100644 (file)
@@ -66,6 +66,9 @@ type Server struct {
 
        flatNewsMux sync.Mutex
        FlatNews    []byte
+
+       banListMU sync.Mutex
+       banList   map[string]*time.Time
 }
 
 type PrivateChat struct {
@@ -184,6 +187,7 @@ func (s *Server) Serve(ctx context.Context, ln net.Listener) error {
                go func() {
                        s.Logger.Infow("Connection established", "RemoteAddr", conn.RemoteAddr())
 
+                       defer conn.Close()
                        if err := s.handleNewConnection(connCtx, conn, conn.RemoteAddr().String()); err != nil {
                                if err == io.EOF {
                                        s.Logger.Infow("Client disconnected", "RemoteAddr", conn.RemoteAddr())
@@ -215,6 +219,7 @@ func NewServer(configDir string, netPort int, logger *zap.SugaredLogger, FS File
                Stats:         &Stats{StartTime: time.Now()},
                ThreadedNews:  &ThreadedNews{},
                FS:            FS,
+               banList:       make(map[string]*time.Time),
        }
 
        var err error
@@ -233,6 +238,9 @@ func NewServer(configDir string, netPort int, logger *zap.SugaredLogger, FS File
                return nil, err
        }
 
+       // try to load the ban list, but ignore errors as this file may not be present or may be empty
+       _ = server.loadBanList(filepath.Join(configDir, "Banlist.yaml"))
+
        if err := server.loadThreadedNews(filepath.Join(configDir, "ThreadedNews.yaml")); err != nil {
                return nil, err
        }
@@ -317,6 +325,22 @@ func (s *Server) keepaliveHandler() {
        }
 }
 
+func (s *Server) writeBanList() error {
+       s.banListMU.Lock()
+       defer s.banListMU.Unlock()
+
+       out, err := yaml.Marshal(s.banList)
+       if err != nil {
+               return err
+       }
+       err = ioutil.WriteFile(
+               filepath.Join(s.ConfigDir, "Banlist.yaml"),
+               out,
+               0666,
+       )
+       return err
+}
+
 func (s *Server) writeThreadedNews() error {
        s.mux.Lock()
        defer s.mux.Unlock()
@@ -448,6 +472,16 @@ func (s *Server) connectedUsers() []Field {
        return connectedUsers
 }
 
+func (s *Server) loadBanList(path string) error {
+       fh, err := os.Open(path)
+       if err != nil {
+               return err
+       }
+       decoder := yaml.NewDecoder(fh)
+
+       return decoder.Decode(s.banList)
+}
+
 // loadThreadedNews loads the threaded news data from disk
 func (s *Server) loadThreadedNews(threadedNewsPath string) error {
        fh, err := os.Open(threadedNewsPath)
@@ -535,6 +569,31 @@ func (s *Server) handleNewConnection(ctx context.Context, rwc io.ReadWriteCloser
        }
 
        c := s.NewClientConn(rwc, remoteAddr)
+
+       // check if remoteAddr is present in the ban list
+       if banUntil, ok := s.banList[strings.Split(remoteAddr, ":")[0]]; ok {
+               // permaban
+               if banUntil == nil {
+                       s.outbox <- *NewTransaction(
+                               tranServerMsg,
+                               c.ID,
+                               NewField(fieldData, []byte("You are permanently banned on this server")),
+                               NewField(fieldChatOptions, []byte{0, 0}),
+                       )
+                       time.Sleep(1 * time.Second)
+                       return nil
+               } else if time.Now().Before(*banUntil) {
+                       s.outbox <- *NewTransaction(
+                               tranServerMsg,
+                               c.ID,
+                               NewField(fieldData, []byte("You are temporarily banned on this server")),
+                               NewField(fieldChatOptions, []byte{0, 0}),
+                       )
+                       time.Sleep(1 * time.Second)
+                       return nil
+               }
+
+       }
        defer c.Disconnect()
 
        encodedLogin := clientLogin.GetField(fieldUserLogin).Data
index 9edecb86366b5125bbb5a853237da421c8a0439a..dda2dcec447bbc46c6f962067490697539b2f68e 100644 (file)
@@ -1024,12 +1024,48 @@ func HandleDisconnectUser(cc *ClientConn, t *Transaction) (res []Transaction, er
                return res, err
        }
 
-       if err := clientConn.Connection.Close(); err != nil {
-               return res, err
+       // If fieldOptions is set, then the client IP is banned in addition to disconnected.
+       // 00 01 = temporary ban
+       // 00 02 = permanent ban
+       if t.GetField(fieldOptions).Data != nil {
+               switch t.GetField(fieldOptions).Data[1] {
+               case 1:
+                       // send message: "You are temporarily banned on this server"
+                       cc.logger.Infow("Disconnect & temporarily ban " + string(clientConn.UserName))
+
+                       res = append(res, *NewTransaction(
+                               tranServerMsg,
+                               clientConn.ID,
+                               NewField(fieldData, []byte("You are temporarily banned on this server")),
+                               NewField(fieldChatOptions, []byte{0, 0}),
+                       ))
+
+                       banUntil := time.Now().Add(tempBanDuration)
+                       cc.Server.banList[strings.Split(clientConn.RemoteAddr, ":")[0]] = &banUntil
+                       cc.Server.writeBanList()
+               case 2:
+                       // send message: "You are permanently banned on this server"
+                       cc.logger.Infow("Disconnect & ban " + string(clientConn.UserName))
+
+                       res = append(res, *NewTransaction(
+                               tranServerMsg,
+                               clientConn.ID,
+                               NewField(fieldData, []byte("You are permanently banned on this server")),
+                               NewField(fieldChatOptions, []byte{0, 0}),
+                       ))
+
+                       cc.Server.banList[strings.Split(clientConn.RemoteAddr, ":")[0]] = nil
+                       cc.Server.writeBanList()
+               }
        }
 
-       res = append(res, cc.NewReply(t))
-       return res, err
+       // TODO: remove this awful hack
+       go func() {
+               time.Sleep(1 * time.Second)
+               clientConn.Disconnect()
+       }()
+
+       return append(res, cc.NewReply(t)), err
 }
 
 // HandleGetNewsCatNameList returns a list of news categories for a path