]>
Commit | Line | Data |
---|---|---|
6988a057 JH |
1 | package hotline |
2 | ||
3 | import ( | |
4 | "bytes" | |
5 | "encoding/binary" | |
6 | "errors" | |
7 | "golang.org/x/crypto/bcrypt" | |
8 | "math/big" | |
9 | "net" | |
10 | ) | |
11 | ||
12 | type byClientID []*ClientConn | |
13 | ||
14 | func (s byClientID) Len() int { | |
15 | return len(s) | |
16 | } | |
17 | ||
18 | func (s byClientID) Swap(i, j int) { | |
19 | s[i], s[j] = s[j], s[i] | |
20 | } | |
21 | ||
22 | func (s byClientID) Less(i, j int) bool { | |
23 | return s[i].uint16ID() < s[j].uint16ID() | |
24 | } | |
25 | ||
26 | // ClientConn represents a client connected to a Server | |
27 | type ClientConn struct { | |
28 | Connection net.Conn | |
29 | ID *[]byte | |
30 | Icon *[]byte | |
31 | Flags *[]byte | |
72dd37f1 | 32 | UserName []byte |
6988a057 | 33 | Account *Account |
61c272e1 | 34 | IdleTime int |
6988a057 JH |
35 | Server *Server |
36 | Version *[]byte | |
37 | Idle bool | |
38 | AutoReply *[]byte | |
39 | Transfers map[int][]*FileTransfer | |
bd1ce113 | 40 | Agreed bool |
6988a057 JH |
41 | } |
42 | ||
43 | func (cc *ClientConn) sendAll(t int, fields ...Field) { | |
44 | for _, c := range sortedClients(cc.Server.Clients) { | |
45 | cc.Server.outbox <- *NewTransaction(t, c.ID, fields...) | |
46 | } | |
47 | } | |
48 | ||
49 | func (cc *ClientConn) handleTransaction(transaction *Transaction) error { | |
50 | requestNum := binary.BigEndian.Uint16(transaction.Type) | |
51 | if handler, ok := TransactionHandlers[requestNum]; ok { | |
52 | for _, reqField := range handler.RequiredFields { | |
53 | field := transaction.GetField(reqField.ID) | |
54 | ||
55 | // Validate that required field is present | |
56 | if field.ID == nil { | |
57 | cc.Server.Logger.Infow( | |
58 | "Missing required field", | |
72dd37f1 | 59 | "Account", cc.Account.Login, "UserName", string(cc.UserName), "RequestType", handler.Name, "FieldID", reqField.ID, |
6988a057 JH |
60 | ) |
61 | return nil | |
62 | } | |
63 | ||
64 | if len(field.Data) < reqField.minLen { | |
65 | cc.Server.Logger.Infow( | |
66 | "Field does not meet minLen", | |
72dd37f1 | 67 | "Account", cc.Account.Login, "UserName", string(cc.UserName), "RequestType", handler.Name, "FieldID", reqField.ID, |
6988a057 JH |
68 | ) |
69 | return nil | |
70 | } | |
71 | } | |
72 | if !authorize(cc.Account.Access, handler.Access) { | |
73 | cc.Server.Logger.Infow( | |
74 | "Unauthorized Action", | |
72dd37f1 | 75 | "Account", cc.Account.Login, "UserName", string(cc.UserName), "RequestType", handler.Name, |
6988a057 JH |
76 | ) |
77 | cc.Server.outbox <- cc.NewErrReply(transaction, handler.DenyMsg) | |
78 | ||
79 | return nil | |
80 | } | |
81 | ||
82 | cc.Server.Logger.Infow( | |
83 | "Received Transaction", | |
84 | "login", cc.Account.Login, | |
72dd37f1 | 85 | "name", string(cc.UserName), |
6988a057 JH |
86 | "RequestType", handler.Name, |
87 | ) | |
88 | ||
89 | transactions, err := handler.Handler(cc, transaction) | |
90 | if err != nil { | |
91 | return err | |
92 | } | |
93 | for _, t := range transactions { | |
94 | cc.Server.outbox <- t | |
95 | } | |
96 | } else { | |
97 | cc.Server.Logger.Errorw( | |
98 | "Unimplemented transaction type received", | |
72dd37f1 | 99 | "UserName", string(cc.UserName), "RequestID", requestNum, |
6988a057 JH |
100 | ) |
101 | } | |
102 | ||
103 | cc.Server.mux.Lock() | |
104 | defer cc.Server.mux.Unlock() | |
105 | ||
61c272e1 JH |
106 | if requestNum != tranKeepAlive { |
107 | // reset the user idle timer | |
108 | cc.IdleTime = 0 | |
109 | ||
110 | // if user was previously idle, mark as not idle and notify other connected clients that | |
111 | // the user is no longer away | |
112 | if cc.Idle { | |
113 | flagBitmap := big.NewInt(int64(binary.BigEndian.Uint16(*cc.Flags))) | |
114 | flagBitmap.SetBit(flagBitmap, userFlagAway, 0) | |
115 | binary.BigEndian.PutUint16(*cc.Flags, uint16(flagBitmap.Int64())) | |
116 | cc.Idle = false | |
117 | ||
118 | cc.sendAll( | |
119 | tranNotifyChangeUser, | |
120 | NewField(fieldUserID, *cc.ID), | |
121 | NewField(fieldUserFlags, *cc.Flags), | |
122 | NewField(fieldUserName, cc.UserName), | |
123 | NewField(fieldUserIconID, *cc.Icon), | |
124 | ) | |
125 | } | |
6988a057 JH |
126 | } |
127 | ||
6988a057 JH |
128 | return nil |
129 | } | |
130 | ||
131 | func (cc *ClientConn) Authenticate(login string, password []byte) bool { | |
132 | if account, ok := cc.Server.Accounts[login]; ok { | |
133 | return bcrypt.CompareHashAndPassword([]byte(account.Password), password) == nil | |
134 | } | |
135 | ||
136 | return false | |
137 | } | |
138 | ||
139 | func (cc *ClientConn) uint16ID() uint16 { | |
140 | id, _ := byteToInt(*cc.ID) | |
141 | return uint16(id) | |
142 | } | |
143 | ||
144 | // Authorize checks if the user account has the specified permission | |
145 | func (cc *ClientConn) Authorize(access int) bool { | |
146 | if access == 0 { | |
147 | return true | |
148 | } | |
149 | ||
150 | accessBitmap := big.NewInt(int64(binary.BigEndian.Uint64(*cc.Account.Access))) | |
151 | ||
152 | return accessBitmap.Bit(63-access) == 1 | |
153 | } | |
154 | ||
155 | // Disconnect notifies other clients that a client has disconnected | |
156 | func (cc ClientConn) Disconnect() { | |
157 | cc.Server.mux.Lock() | |
158 | defer cc.Server.mux.Unlock() | |
159 | ||
160 | delete(cc.Server.Clients, binary.BigEndian.Uint16(*cc.ID)) | |
161 | ||
162 | cc.NotifyOthers(*NewTransaction(tranNotifyDeleteUser, nil, NewField(fieldUserID, *cc.ID))) | |
163 | ||
164 | if err := cc.Connection.Close(); err != nil { | |
165 | cc.Server.Logger.Errorw("error closing client connection", "RemoteAddr", cc.Connection.RemoteAddr()) | |
166 | } | |
167 | } | |
168 | ||
169 | // NotifyOthers sends transaction t to other clients connected to the server | |
170 | func (cc ClientConn) NotifyOthers(t Transaction) { | |
171 | for _, c := range sortedClients(cc.Server.Clients) { | |
bd1ce113 | 172 | if c.ID != cc.ID && c.Agreed { |
6988a057 JH |
173 | t.clientID = c.ID |
174 | cc.Server.outbox <- t | |
175 | } | |
176 | } | |
177 | } | |
178 | ||
179 | type handshake struct { | |
180 | Protocol [4]byte // Must be 0x54525450 TRTP | |
181 | SubProtocol [4]byte | |
182 | Version [2]byte // Always 1 | |
183 | SubVersion [2]byte | |
184 | } | |
185 | ||
186 | // Handshake | |
187 | // After establishing TCP connection, both client and server start the handshake process | |
188 | // in order to confirm that each of them comply with requirements of the other. | |
189 | // The information provided in this initial data exchange identifies protocols, | |
190 | // and their versions, used in the communication. In the case where, after inspection, | |
191 | // the capabilities of one of the subjects do not comply with the requirements of the other, | |
192 | // the connection is dropped. | |
193 | // | |
194 | // The following information is sent to the server: | |
195 | // Description Size Data Note | |
196 | // Protocol ID 4 TRTP 0x54525450 | |
197 | // Sub-protocol ID 4 HOTL User defined | |
198 | // VERSION 2 1 Currently 1 | |
199 | // Sub-version 2 2 User defined | |
200 | // | |
201 | // The server replies with the following: | |
202 | // Description Size Data Note | |
203 | // Protocol ID 4 TRTP | |
204 | //Error code 4 Error code returned by the server (0 = no error) | |
5c34f875 | 205 | func Handshake(conn net.Conn, buf []byte) error { |
6988a057 JH |
206 | var h handshake |
207 | r := bytes.NewReader(buf) | |
208 | if err := binary.Read(r, binary.BigEndian, &h); err != nil { | |
209 | return err | |
210 | } | |
211 | ||
212 | if h.Protocol != [4]byte{0x54, 0x52, 0x54, 0x50} { | |
213 | return errors.New("invalid handshake") | |
214 | } | |
215 | ||
216 | _, err := conn.Write([]byte{84, 82, 84, 80, 0, 0, 0, 0}) | |
217 | return err | |
218 | } | |
219 | ||
220 | // NewReply returns a reply Transaction with fields for the ClientConn | |
221 | func (cc *ClientConn) NewReply(t *Transaction, fields ...Field) Transaction { | |
222 | reply := Transaction{ | |
223 | Flags: 0x00, | |
224 | IsReply: 0x01, | |
225 | Type: t.Type, | |
226 | ID: t.ID, | |
227 | clientID: cc.ID, | |
228 | ErrorCode: []byte{0, 0, 0, 0}, | |
229 | Fields: fields, | |
230 | } | |
231 | ||
232 | return reply | |
233 | } | |
234 | ||
235 | // NewErrReply returns an error reply Transaction with errMsg | |
236 | func (cc *ClientConn) NewErrReply(t *Transaction, errMsg string) Transaction { | |
237 | return Transaction{ | |
238 | clientID: cc.ID, | |
239 | Flags: 0x00, | |
240 | IsReply: 0x01, | |
241 | Type: []byte{0, 0}, | |
242 | ID: t.ID, | |
243 | ErrorCode: []byte{0, 0, 0, 1}, | |
244 | Fields: []Field{ | |
245 | NewField(fieldError, []byte(errMsg)), | |
246 | }, | |
247 | } | |
248 | } |