flatNewsMux sync.Mutex
FlatNews []byte
+
+ banListMU sync.Mutex
+ banList map[string]*time.Time
}
type PrivateChat struct {
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())
Stats: &Stats{StartTime: time.Now()},
ThreadedNews: &ThreadedNews{},
FS: FS,
+ banList: make(map[string]*time.Time),
}
var err error
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
}
if c.IdleTime > userIdleSeconds && !c.Idle {
c.Idle = true
- flagBitmap := big.NewInt(int64(binary.BigEndian.Uint16(*c.Flags)))
+ flagBitmap := big.NewInt(int64(binary.BigEndian.Uint16(c.Flags)))
flagBitmap.SetBit(flagBitmap, userFlagAway, 1)
- binary.BigEndian.PutUint16(*c.Flags, uint16(flagBitmap.Int64()))
+ binary.BigEndian.PutUint16(c.Flags, uint16(flagBitmap.Int64()))
c.sendAll(
tranNotifyChangeUser,
NewField(fieldUserID, *c.ID),
- NewField(fieldUserFlags, *c.Flags),
+ NewField(fieldUserFlags, c.Flags),
NewField(fieldUserName, c.UserName),
- NewField(fieldUserIconID, *c.Icon),
+ NewField(fieldUserIconID, c.Icon),
)
}
}
}
}
+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()
clientConn := &ClientConn{
ID: &[]byte{0, 0},
- Icon: &[]byte{0, 0},
- Flags: &[]byte{0, 0},
+ Icon: []byte{0, 0},
+ Flags: []byte{0, 0},
UserName: []byte{},
Connection: conn,
Server: s,
- Version: &[]byte{},
+ Version: []byte{},
AutoReply: []byte{},
transfers: map[int]map[[4]byte]*FileTransfer{},
Agreed: false,
}
user := User{
ID: *c.ID,
- Icon: *c.Icon,
- Flags: *c.Flags,
+ Icon: c.Icon,
+ Flags: c.Flags,
Name: string(c.UserName),
}
connectedUsers = append(connectedUsers, NewField(fieldUsernameWithInfo, user.Payload()))
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)
}
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
encodedPassword := clientLogin.GetField(fieldUserPassword).Data
- *c.Version = clientLogin.GetField(fieldVersion).Data
+ c.Version = clientLogin.GetField(fieldVersion).Data
var login string
for _, char := range encodedLogin {
return err
}
- c.logger.Infow("Login failed", "clientVersion", fmt.Sprintf("%x", *c.Version))
+ c.logger.Infow("Login failed", "clientVersion", fmt.Sprintf("%x", c.Version))
return nil
}
- if clientLogin.GetField(fieldUserName).Data != nil {
- c.UserName = clientLogin.GetField(fieldUserName).Data
- }
-
if clientLogin.GetField(fieldUserIconID).Data != nil {
- *c.Icon = clientLogin.GetField(fieldUserIconID).Data
+ c.Icon = clientLogin.GetField(fieldUserIconID).Data
}
c.Account = c.Server.Accounts[login]
+ if clientLogin.GetField(fieldUserName).Data != nil {
+ if c.Authorize(accessAnyName) {
+ c.UserName = clientLogin.GetField(fieldUserName).Data
+ } else {
+ c.UserName = []byte(c.Account.Name)
+ }
+ }
+
if c.Authorize(accessDisconUser) {
- *c.Flags = []byte{0, 2}
+ c.Flags = []byte{0, 2}
}
s.outbox <- c.NewReply(clientLogin,
// Send user access privs so client UI knows how to behave
c.Server.outbox <- *NewTransaction(tranUserAccess, c.ID, NewField(fieldUserAccess, *c.Account.Access))
- // Show agreement to client
- c.Server.outbox <- *NewTransaction(tranShowAgreement, c.ID, NewField(fieldData, s.Agreement))
+ // Users with accessNoAgreement do not receive the server agreement on login
+ if c.Authorize(accessNoAgreement) {
+ c.Server.outbox <- *NewTransaction(tranShowAgreement, c.ID, NewField(fieldNoServerAgreement, []byte{1}))
+ } else {
+ c.Server.outbox <- *NewTransaction(tranShowAgreement, c.ID, NewField(fieldData, s.Agreement))
+ }
// Used simplified hotline v1.2.3 login flow for clients that do not send login info in tranAgreed
- if *c.Version == nil || bytes.Equal(*c.Version, nostalgiaVersion) {
+ if c.Version == nil || bytes.Equal(c.Version, nostalgiaVersion) {
c.Agreed = true
c.logger = c.logger.With("name", string(c.UserName))
- c.logger.Infow("Login successful", "clientVersion", fmt.Sprintf("%x", *c.Version))
+ c.logger.Infow("Login successful", "clientVersion", fmt.Sprintf("%x", c.Version))
for _, t := range c.notifyOthers(
*NewTransaction(
tranNotifyChangeUser, nil,
NewField(fieldUserName, c.UserName),
NewField(fieldUserID, *c.ID),
- NewField(fieldUserIconID, *c.Icon),
- NewField(fieldUserFlags, *c.Flags),
+ NewField(fieldUserIconID, c.Icon),
+ NewField(fieldUserFlags, c.Flags),
),
) {
c.Server.outbox <- t