]> git.r.bdr.sh - rbdr/mobius/blame - hotline/client_conn.go
Re-add UserList field to client
[rbdr/mobius] / hotline / client_conn.go
CommitLineData
6988a057
JH
1package hotline
2
3import (
a2ef262a 4 "cmp"
6988a057 5 "encoding/binary"
df1ade54 6 "fmt"
6988a057 7 "golang.org/x/crypto/bcrypt"
d4c152a4 8 "io"
a6216dd8 9 "log/slog"
df1ade54
JH
10 "strings"
11 "sync"
6988a057
JH
12)
13
d9bc63a1
JH
14var clientConnSortFunc = func(a, b *ClientConn) int {
15 return cmp.Compare(
16 binary.BigEndian.Uint16(a.ID[:]),
17 binary.BigEndian.Uint16(b.ID[:]),
18 )
19}
20
6988a057
JH
21// ClientConn represents a client connected to a Server
22type ClientConn struct {
d4c152a4
JH
23 Connection io.ReadWriteCloser
24 RemoteAddr string
d9bc63a1
JH
25 ID ClientID
26 Icon []byte // TODO: make fixed size of 2
27 Version []byte // TODO: make fixed size of 2
28
29 flagsMU sync.Mutex // TODO: move into UserFlags struct
30 Flags UserFlags
31
32 UserName []byte
33 Account *Account
34 IdleTime int
35 Server *Server // TODO: consider adding methods to interact with server
36 AutoReply []byte
37
38 ClientFileTransferMgr ClientFileTransferMgr
df1ade54 39
a6216dd8 40 logger *slog.Logger
a2ef262a 41
d9bc63a1
JH
42 mu sync.RWMutex
43}
44
45type ClientFileTransferMgr struct {
46 transfers map[FileTransferType]map[FileTransferID]*FileTransfer
47
48 mu sync.RWMutex
49}
50
51func NewClientFileTransferMgr() ClientFileTransferMgr {
52 return ClientFileTransferMgr{
53 transfers: map[FileTransferType]map[FileTransferID]*FileTransfer{
54 FileDownload: {},
55 FileUpload: {},
56 FolderDownload: {},
57 FolderUpload: {},
58 BannerDownload: {},
59 },
60 }
61}
62
63func (cftm *ClientFileTransferMgr) Add(ftType FileTransferType, ft *FileTransfer) {
64 cftm.mu.Lock()
65 defer cftm.mu.Unlock()
66
67 cftm.transfers[ftType][ft.refNum] = ft
68}
69
70func (cftm *ClientFileTransferMgr) Get(ftType FileTransferType) []FileTransfer {
71 cftm.mu.Lock()
72 defer cftm.mu.Unlock()
73
74 fts := cftm.transfers[ftType]
75
76 var transfers []FileTransfer
77 for _, ft := range fts {
78 transfers = append(transfers, *ft)
79 }
80
81 return transfers
6988a057
JH
82}
83
d9bc63a1
JH
84func (cftm *ClientFileTransferMgr) Delete(ftType FileTransferType, id FileTransferID) {
85 cftm.mu.Lock()
86 defer cftm.mu.Unlock()
87
88 delete(cftm.transfers[ftType], id)
89}
90
91func (cc *ClientConn) SendAll(t [2]byte, fields ...Field) {
92 for _, c := range cc.Server.ClientMgr.List() {
a2ef262a 93 cc.Server.outbox <- NewTransaction(t, c.ID, fields...)
6988a057
JH
94 }
95}
96
a2ef262a
JH
97func (cc *ClientConn) handleTransaction(transaction Transaction) {
98 if handler, ok := TransactionHandlers[transaction.Type]; ok {
d9bc63a1
JH
99 if transaction.Type != TranKeepAlive {
100 cc.logger.Info(tranTypeNames[transaction.Type])
101 }
6988a057 102
a2ef262a 103 for _, t := range handler(cc, &transaction) {
6988a057
JH
104 cc.Server.outbox <- t
105 }
6988a057
JH
106 }
107
a2ef262a 108 if transaction.Type != TranKeepAlive {
d9bc63a1
JH
109 cc.mu.Lock()
110 defer cc.mu.Unlock()
111
61c272e1
JH
112 // reset the user idle timer
113 cc.IdleTime = 0
114
115 // if user was previously idle, mark as not idle and notify other connected clients that
116 // the user is no longer away
d9bc63a1 117 if cc.Flags.IsSet(UserFlagAway) {
a2ef262a 118 cc.Flags.Set(UserFlagAway, 0)
61c272e1 119
d9bc63a1 120 cc.SendAll(
d005ef04 121 TranNotifyChangeUser,
a2ef262a
JH
122 NewField(FieldUserID, cc.ID[:]),
123 NewField(FieldUserFlags, cc.Flags[:]),
d005ef04
JH
124 NewField(FieldUserName, cc.UserName),
125 NewField(FieldUserIconID, cc.Icon),
61c272e1
JH
126 )
127 }
6988a057 128 }
6988a057
JH
129}
130
131func (cc *ClientConn) Authenticate(login string, password []byte) bool {
d9bc63a1 132 if account := cc.Server.AccountManager.Get(login); account != nil {
6988a057
JH
133 return bcrypt.CompareHashAndPassword([]byte(account.Password), password) == nil
134 }
135
136 return false
137}
138
6988a057
JH
139// Authorize checks if the user account has the specified permission
140func (cc *ClientConn) Authorize(access int) bool {
a2ef262a
JH
141 if cc.Account == nil {
142 return false
143 }
187d6dc5 144 return cc.Account.Access.IsSet(access)
6988a057
JH
145}
146
147// Disconnect notifies other clients that a client has disconnected
0a92e50b 148func (cc *ClientConn) Disconnect() {
d9bc63a1 149 cc.Server.ClientMgr.Delete(cc.ID)
6988a057 150
d9bc63a1 151 for _, t := range cc.NotifyOthers(NewTransaction(TranNotifyDeleteUser, [2]byte{}, NewField(FieldUserID, cc.ID[:]))) {
21581958
JH
152 cc.Server.outbox <- t
153 }
6988a057
JH
154
155 if err := cc.Connection.Close(); err != nil {
a6216dd8 156 cc.Server.Logger.Error("error closing client connection", "RemoteAddr", cc.RemoteAddr)
6988a057
JH
157 }
158}
159
d9bc63a1
JH
160// NotifyOthers sends transaction t to other clients connected to the server
161func (cc *ClientConn) NotifyOthers(t Transaction) (trans []Transaction) {
162 for _, c := range cc.Server.ClientMgr.List() {
5853654f 163 if c.ID != cc.ID {
6988a057 164 t.clientID = c.ID
21581958 165 trans = append(trans, t)
6988a057
JH
166 }
167 }
21581958 168 return trans
6988a057
JH
169}
170
6988a057
JH
171// NewReply returns a reply Transaction with fields for the ClientConn
172func (cc *ClientConn) NewReply(t *Transaction, fields ...Field) Transaction {
95159e55 173 return Transaction{
a2ef262a
JH
174 IsReply: 1,
175 ID: t.ID,
176 clientID: cc.ID,
177 Fields: fields,
6988a057 178 }
6988a057
JH
179}
180
181// NewErrReply returns an error reply Transaction with errMsg
a2ef262a
JH
182func (cc *ClientConn) NewErrReply(t *Transaction, errMsg string) []Transaction {
183 return []Transaction{
184 {
185 clientID: cc.ID,
186 IsReply: 1,
187 ID: t.ID,
188 ErrorCode: [4]byte{0, 0, 0, 1},
189 Fields: []Field{
190 NewField(FieldError, []byte(errMsg)),
191 },
6988a057
JH
192 },
193 }
194}
7cd900d6 195
df1ade54
JH
196const userInfoTemplate = `Nickname: %s
197Name: %s
198Account: %s
199Address: %s
200
201-------- File Downloads ---------
202
203%s
204------- Folder Downloads --------
205
206%s
207--------- File Uploads ----------
208
209%s
210-------- Folder Uploads ---------
211
212%s
213------- Waiting Downloads -------
214
215%s
216`
217
d9bc63a1 218func formatDownloadList(fts []FileTransfer) (s string) {
df1ade54
JH
219 if len(fts) == 0 {
220 return "None.\n"
221 }
222
223 for _, dl := range fts {
224 s += dl.String()
225 }
226
227 return s
228}
229
230func (cc *ClientConn) String() string {
df1ade54
JH
231 template := fmt.Sprintf(
232 userInfoTemplate,
233 cc.UserName,
234 cc.Account.Name,
235 cc.Account.Login,
236 cc.RemoteAddr,
d9bc63a1
JH
237 formatDownloadList(cc.ClientFileTransferMgr.Get(FileDownload)),
238 formatDownloadList(cc.ClientFileTransferMgr.Get(FolderDownload)),
239 formatDownloadList(cc.ClientFileTransferMgr.Get(FileUpload)),
240 formatDownloadList(cc.ClientFileTransferMgr.Get(FolderUpload)),
df1ade54
JH
241 "None.\n",
242 )
243
c8bfd606 244 return strings.ReplaceAll(template, "\n", "\r")
df1ade54 245}