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