]> git.r.bdr.sh - rbdr/mobius/blob - hotline/client_conn.go
4a471b3382b4533160bc62d89fc0afc5a54280f4
[rbdr/mobius] / hotline / client_conn.go
1 package hotline
2
3 import (
4 "encoding/binary"
5 "golang.org/x/crypto/bcrypt"
6 "math/big"
7 "net"
8 )
9
10 type byClientID []*ClientConn
11
12 func (s byClientID) Len() int {
13 return len(s)
14 }
15
16 func (s byClientID) Swap(i, j int) {
17 s[i], s[j] = s[j], s[i]
18 }
19
20 func (s byClientID) Less(i, j int) bool {
21 return s[i].uint16ID() < s[j].uint16ID()
22 }
23
24 // ClientConn represents a client connected to a Server
25 type ClientConn struct {
26 Connection net.Conn
27 ID *[]byte
28 Icon *[]byte
29 Flags *[]byte
30 UserName []byte
31 Account *Account
32 IdleTime int
33 Server *Server
34 Version *[]byte
35 Idle bool
36 AutoReply []byte
37 Transfers map[int][]*FileTransfer
38 Agreed bool
39 }
40
41 func (cc *ClientConn) sendAll(t int, fields ...Field) {
42 for _, c := range sortedClients(cc.Server.Clients) {
43 cc.Server.outbox <- *NewTransaction(t, c.ID, fields...)
44 }
45 }
46
47 func (cc *ClientConn) handleTransaction(transaction *Transaction) error {
48 requestNum := binary.BigEndian.Uint16(transaction.Type)
49 if handler, ok := TransactionHandlers[requestNum]; ok {
50 for _, reqField := range handler.RequiredFields {
51 field := transaction.GetField(reqField.ID)
52
53 // Validate that required field is present
54 if field.ID == nil {
55 cc.Server.Logger.Errorw(
56 "Missing required field",
57 "Account", cc.Account.Login, "UserName", string(cc.UserName), "RequestType", handler.Name, "FieldID", reqField.ID,
58 )
59 return nil
60 }
61
62 if len(field.Data) < reqField.minLen {
63 cc.Server.Logger.Infow(
64 "Field does not meet minLen",
65 "Account", cc.Account.Login, "UserName", string(cc.UserName), "RequestType", handler.Name, "FieldID", reqField.ID,
66 )
67 return nil
68 }
69 }
70 if !authorize(cc.Account.Access, handler.Access) {
71 cc.Server.Logger.Infow(
72 "Unauthorized Action",
73 "Account", cc.Account.Login, "UserName", string(cc.UserName), "RequestType", handler.Name,
74 )
75 cc.Server.outbox <- cc.NewErrReply(transaction, handler.DenyMsg)
76
77 return nil
78 }
79
80 cc.Server.Logger.Infow(
81 "Received Transaction",
82 "login", cc.Account.Login,
83 "name", string(cc.UserName),
84 "RequestType", handler.Name,
85 )
86
87 transactions, err := handler.Handler(cc, transaction)
88 if err != nil {
89 return err
90 }
91 for _, t := range transactions {
92 cc.Server.outbox <- t
93 }
94 } else {
95 cc.Server.Logger.Errorw(
96 "Unimplemented transaction type received",
97 "UserName", string(cc.UserName), "RequestID", requestNum,
98 )
99 }
100
101 cc.Server.mux.Lock()
102 defer cc.Server.mux.Unlock()
103
104 if requestNum != tranKeepAlive {
105 // reset the user idle timer
106 cc.IdleTime = 0
107
108 // if user was previously idle, mark as not idle and notify other connected clients that
109 // the user is no longer away
110 if cc.Idle {
111 flagBitmap := big.NewInt(int64(binary.BigEndian.Uint16(*cc.Flags)))
112 flagBitmap.SetBit(flagBitmap, userFlagAway, 0)
113 binary.BigEndian.PutUint16(*cc.Flags, uint16(flagBitmap.Int64()))
114 cc.Idle = false
115
116 cc.sendAll(
117 tranNotifyChangeUser,
118 NewField(fieldUserID, *cc.ID),
119 NewField(fieldUserFlags, *cc.Flags),
120 NewField(fieldUserName, cc.UserName),
121 NewField(fieldUserIconID, *cc.Icon),
122 )
123 }
124 }
125
126 return nil
127 }
128
129 func (cc *ClientConn) Authenticate(login string, password []byte) bool {
130 if account, ok := cc.Server.Accounts[login]; ok {
131 return bcrypt.CompareHashAndPassword([]byte(account.Password), password) == nil
132 }
133
134 return false
135 }
136
137 func (cc *ClientConn) uint16ID() uint16 {
138 id, _ := byteToInt(*cc.ID)
139 return uint16(id)
140 }
141
142 // Authorize checks if the user account has the specified permission
143 func (cc *ClientConn) Authorize(access int) bool {
144 if access == 0 {
145 return true
146 }
147
148 accessBitmap := big.NewInt(int64(binary.BigEndian.Uint64(*cc.Account.Access)))
149
150 return accessBitmap.Bit(63-access) == 1
151 }
152
153 // Disconnect notifies other clients that a client has disconnected
154 func (cc ClientConn) Disconnect() {
155 cc.Server.mux.Lock()
156 defer cc.Server.mux.Unlock()
157
158 delete(cc.Server.Clients, binary.BigEndian.Uint16(*cc.ID))
159
160 cc.notifyOthers(*NewTransaction(tranNotifyDeleteUser, nil, NewField(fieldUserID, *cc.ID)))
161
162 if err := cc.Connection.Close(); err != nil {
163 cc.Server.Logger.Errorw("error closing client connection", "RemoteAddr", cc.Connection.RemoteAddr())
164 }
165 }
166
167 // notifyOthers sends transaction t to other clients connected to the server
168 func (cc ClientConn) notifyOthers(t Transaction) {
169 for _, c := range sortedClients(cc.Server.Clients) {
170 if c.ID != cc.ID && c.Agreed {
171 t.clientID = c.ID
172 cc.Server.outbox <- t
173 }
174 }
175 }
176
177 // NewReply returns a reply Transaction with fields for the ClientConn
178 func (cc *ClientConn) NewReply(t *Transaction, fields ...Field) Transaction {
179 reply := Transaction{
180 Flags: 0x00,
181 IsReply: 0x01,
182 Type: t.Type,
183 ID: t.ID,
184 clientID: cc.ID,
185 ErrorCode: []byte{0, 0, 0, 0},
186 Fields: fields,
187 }
188
189 return reply
190 }
191
192 // NewErrReply returns an error reply Transaction with errMsg
193 func (cc *ClientConn) NewErrReply(t *Transaction, errMsg string) Transaction {
194 return Transaction{
195 clientID: cc.ID,
196 Flags: 0x00,
197 IsReply: 0x01,
198 Type: []byte{0, 0},
199 ID: t.ID,
200 ErrorCode: []byte{0, 0, 0, 1},
201 Fields: []Field{
202 NewField(fieldError, []byte(errMsg)),
203 },
204 }
205 }