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