5 "golang.org/x/crypto/bcrypt"
10 type byClientID []*ClientConn
12 func (s byClientID) Len() int {
16 func (s byClientID) Swap(i, j int) {
17 s[i], s[j] = s[j], s[i]
20 func (s byClientID) Less(i, j int) bool {
21 return s[i].uint16ID() < s[j].uint16ID()
24 // ClientConn represents a client connected to a Server
25 type ClientConn struct {
37 Transfers map[int][]*FileTransfer
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...)
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)
53 // Validate that required field is present
55 cc.Server.Logger.Errorw(
56 "Missing required field",
57 "Account", cc.Account.Login, "UserName", string(cc.UserName), "RequestType", handler.Name, "FieldID", reqField.ID,
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,
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,
75 cc.Server.outbox <- cc.NewErrReply(transaction, handler.DenyMsg)
80 cc.Server.Logger.Infow(
81 "Received Transaction",
82 "login", cc.Account.Login,
83 "name", string(cc.UserName),
84 "RequestType", handler.Name,
87 transactions, err := handler.Handler(cc, transaction)
91 for _, t := range transactions {
95 cc.Server.Logger.Errorw(
96 "Unimplemented transaction type received",
97 "UserName", string(cc.UserName), "RequestID", requestNum,
102 defer cc.Server.mux.Unlock()
104 if requestNum != tranKeepAlive {
105 // reset the user idle timer
108 // if user was previously idle, mark as not idle and notify other connected clients that
109 // the user is no longer away
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()))
117 tranNotifyChangeUser,
118 NewField(fieldUserID, *cc.ID),
119 NewField(fieldUserFlags, *cc.Flags),
120 NewField(fieldUserName, cc.UserName),
121 NewField(fieldUserIconID, *cc.Icon),
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
137 func (cc *ClientConn) uint16ID() uint16 {
138 id, _ := byteToInt(*cc.ID)
142 // Authorize checks if the user account has the specified permission
143 func (cc *ClientConn) Authorize(access int) bool {
148 accessBitmap := big.NewInt(int64(binary.BigEndian.Uint64(*cc.Account.Access)))
150 return accessBitmap.Bit(63-access) == 1
153 // Disconnect notifies other clients that a client has disconnected
154 func (cc ClientConn) Disconnect() {
156 defer cc.Server.mux.Unlock()
158 delete(cc.Server.Clients, binary.BigEndian.Uint16(*cc.ID))
160 cc.notifyOthers(*NewTransaction(tranNotifyDeleteUser, nil, NewField(fieldUserID, *cc.ID)))
162 if err := cc.Connection.Close(); err != nil {
163 cc.Server.Logger.Errorw("error closing client connection", "RemoteAddr", cc.Connection.RemoteAddr())
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 {
172 cc.Server.outbox <- t
177 // NewReply returns a reply Transaction with fields for the ClientConn
178 func (cc *ClientConn) NewReply(t *Transaction, fields ...Field) Transaction {
179 reply := Transaction{
185 ErrorCode: []byte{0, 0, 0, 0},
192 // NewErrReply returns an error reply Transaction with errMsg
193 func (cc *ClientConn) NewErrReply(t *Transaction, errMsg string) Transaction {
200 ErrorCode: []byte{0, 0, 0, 1},
202 NewField(fieldError, []byte(errMsg)),