import (
"encoding/binary"
"golang.org/x/crypto/bcrypt"
+ "io"
"math/big"
- "net"
)
type byClientID []*ClientConn
return s[i].uint16ID() < s[j].uint16ID()
}
+const template = `Nickname: %s
+Name: %s
+Account: %s
+Address: %s
+
+-------- File Downloads ---------
+
+%s
+
+------- Folder Downloads --------
+
+None.
+
+--------- File Uploads ----------
+
+None.
+
+-------- Folder Uploads ---------
+
+None.
+
+------- Waiting Downloads -------
+
+None.
+
+ `
+
// ClientConn represents a client connected to a Server
type ClientConn struct {
- Connection net.Conn
+ Connection io.ReadWriteCloser
+ RemoteAddr string
ID *[]byte
Icon *[]byte
Flags *[]byte
return nil
}
}
- if !authorize(cc.Account.Access, handler.Access) {
- cc.Server.Logger.Infow(
- "Unauthorized Action",
- "Account", cc.Account.Login, "UserName", string(cc.UserName), "RequestType", handler.Name,
- )
- cc.Server.outbox <- cc.NewErrReply(transaction, handler.DenyMsg)
-
- return nil
- }
cc.Server.Logger.Infow(
"Received Transaction",
cc.notifyOthers(*NewTransaction(tranNotifyDeleteUser, nil, NewField(fieldUserID, *cc.ID)))
if err := cc.Connection.Close(); err != nil {
- cc.Server.Logger.Errorw("error closing client connection", "RemoteAddr", cc.Connection.RemoteAddr())
+ cc.Server.Logger.Errorw("error closing client connection", "RemoteAddr", cc.RemoteAddr)
}
}
import (
"fmt"
- "github.com/davecgh/go-spew/spew"
"github.com/stretchr/testify/assert"
"os"
"testing"
t.Run(tt.name, func(t *testing.T) {
ffif := &FlatFileInformationFork{}
tt.wantErr(t, ffif.UnmarshalBinary(tt.args.b), fmt.Sprintf("UnmarshalBinary(%v)", tt.args.b))
-
- spew.Dump(ffif)
})
}
}
"bytes"
"encoding/binary"
"errors"
- "net"
+ "io"
)
type handshake struct {
// Description Size Data Note
// Protocol ID 4 TRTP
// Error code 4 Error code returned by the server (0 = no error)
-func Handshake(conn net.Conn, buf []byte) error {
+func Handshake(conn io.ReadWriter) error {
+ handshakeBuf := make([]byte, 12)
+ if _, err := io.ReadFull(conn, handshakeBuf); err != nil {
+ return err
+ }
+
var h handshake
- r := bytes.NewReader(buf)
+ r := bytes.NewReader(handshakeBuf)
if err := binary.Read(r, binary.BigEndian, &h); err != nil {
return err
}
"IsReply", t.IsReply,
"type", handler.Name,
"sentBytes", n,
- "remoteAddr", client.Connection.RemoteAddr(),
+ "remoteAddr", client.RemoteAddr,
)
return nil
}
}
}()
go func() {
- if err := s.handleNewConnection(conn); err != nil {
+ if err := s.handleNewConnection(conn, conn.RemoteAddr().String()); err != nil {
if err == io.EOF {
s.Logger.Infow("Client disconnected", "RemoteAddr", conn.RemoteAddr())
} else {
return err
}
-func (s *Server) NewClientConn(conn net.Conn) *ClientConn {
+func (s *Server) NewClientConn(conn net.Conn, remoteAddr string) *ClientConn {
s.mux.Lock()
defer s.mux.Unlock()
AutoReply: []byte{},
Transfers: make(map[int][]*FileTransfer),
Agreed: false,
+ RemoteAddr: remoteAddr,
}
*s.NextGuestID++
ID := *s.NextGuestID
}
// handleNewConnection takes a new net.Conn and performs the initial login sequence
-func (s *Server) handleNewConnection(conn net.Conn) error {
+func (s *Server) handleNewConnection(conn net.Conn, remoteAddr string) error {
defer dontPanic(s.Logger)
- handshakeBuf := make([]byte, 12)
- if _, err := io.ReadFull(conn, handshakeBuf); err != nil {
- return err
- }
- if err := Handshake(conn, handshakeBuf); err != nil {
+ if err := Handshake(conn); err != nil {
return err
}
return err
}
- c := s.NewClientConn(conn)
+ c := s.NewClientConn(conn, remoteAddr)
defer c.Disconnect()
encodedLogin := clientLogin.GetField(fieldUserLogin).Data
*c.Flags = []byte{0, 2}
}
- s.Logger.Infow("Client connection received", "login", login, "version", *c.Version, "RemoteAddr", conn.RemoteAddr().String())
+ s.Logger.Infow("Client connection received", "login", login, "version", *c.Version, "RemoteAddr", remoteAddr)
s.outbox <- c.NewReply(clientLogin,
NewField(fieldVersion, []byte{0x00, 0xbe}),
package hotline
import (
- "bytes"
- "context"
- "fmt"
- "github.com/davecgh/go-spew/spew"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
- "net"
"os"
"testing"
)
-type testCase struct {
- name string // test case description
- account Account // Account struct for a user that will test transaction will execute under
- request *Transaction // transaction that will be sent by the client to the server
- setup func() // Optional test-specific setup required for the scenario
- teardown func() // Optional test-specific teardown for the scenario
- mockHandler map[int]*mockClientHandler
-}
-
-func (tt *testCase) Setup(srv *Server) error {
- if err := srv.NewUser(tt.account.Login, tt.account.Name, string(negateString([]byte(tt.account.Password))), *tt.account.Access); err != nil {
- return err
- }
-
- if tt.setup != nil {
- tt.setup()
- }
-
- return nil
-}
-
-func (tt *testCase) Teardown(srv *Server) error {
- if err := srv.DeleteUser(tt.account.Login); err != nil {
- return err
- }
-
- if tt.teardown != nil {
- tt.teardown()
- }
-
- return nil
-}
-
func NewTestLogger() *zap.SugaredLogger {
encoderCfg := zap.NewProductionEncoderConfig()
encoderCfg.TimeKey = "timestamp"
return l.Sugar()
}
-func StartTestServer() (*Server, context.Context, context.CancelFunc) {
- ctx, cancelRoot := context.WithCancel(context.Background())
-
- FS = &OSFileStore{}
-
- srv, err := NewServer("test/config/", "localhost", 0, NewTestLogger())
- if err != nil {
- panic(err)
- }
-
- go func() {
- err := srv.ListenAndServe(ctx, cancelRoot)
- if err != nil {
- panic(err)
- }
- }()
-
- return srv, ctx, cancelRoot
-}
-
-func TestHandshake(t *testing.T) {
- mfs := &MockFileStore{}
- fh, _ := os.Open("./test/config/Agreement.txt")
- mfs.On("Open", "/test/config/Agreement.txt").Return(fh, nil)
- fh, _ = os.Open("./test/config/config.yaml")
- mfs.On("Open", "/test/config/config.yaml").Return(fh, nil)
- FS = mfs
- spew.Dump(mfs)
-
- srv, _, cancelFunc := StartTestServer()
- defer cancelFunc()
-
- port := srv.APIListener.Addr().(*net.TCPAddr).Port
-
- conn, err := net.Dial("tcp", fmt.Sprintf(":%v", port))
- if err != nil {
- t.Fatal(err)
- }
- defer conn.Close()
-
- conn.Write([]byte{0x54, 0x52, 0x54, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02})
-
- replyBuf := make([]byte, 8)
- _, _ = conn.Read(replyBuf)
-
- want := []byte{84, 82, 84, 80, 0, 0, 0, 0}
- if bytes.Compare(replyBuf, want) != 0 {
- t.Errorf("%q, want %q", replyBuf, want)
- }
-
-}
-
-// func TestLogin(t *testing.T) {
-//
-// tests := []struct {
-// name string
-// client *Client
-// }{
-// {
-// name: "when login is successful",
-// client: NewClient("guest", NewTestLogger()),
-// },
-// }
-// for _, test := range tests {
-// t.Run(test.name, func(t *testing.T) {
-//
-// })
-// }
-// }
-
-func TestNewUser(t *testing.T) {
- srv, _, _ := StartTestServer()
-
- tests := []testCase{
- // {
- // name: "a valid new account",
- // mockHandler: func() mockClientHandler {
- // mh := mockClientHandler{}
- // mh.On("Handle", mock.AnythingOfType("*hotline.Client"), mock.MatchedBy(func(t *Transaction) bool {
- // println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")
- // spew.Dump(t.Type)
- // spew.Dump(bytes.Equal(t.Type, []byte{0x01, 0x5e}))
- // //if !bytes.Equal(t.GetField(fieldError).Data, []byte("You are not allowed to create new accounts.")) {
- // // return false
- // //}
- // return bytes.Equal(t.Type, []byte{0x01, 0x5e},
- // )
- // })).Return(
- // []Transaction{}, nil,
- // )
- //
- // clientHandlers[tranNewUser] = mh
- // return mh
- // }(),
- // client: func() *Client {
- // c := NewClient("testUser", NewTestLogger())
- // return c
- // }(),
- // teardown: func() {
- // _ = srv.DeleteUser("testUser")
- // },
- // account: Account{
- // Login: "test",
- // Name: "unnamed",
- // Password: "test",
- // Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
- // },
- // request: NewTransaction(
- // tranNewUser, nil,
- // NewField(fieldUserLogin, []byte(NegatedUserString([]byte("testUser")))),
- // NewField(fieldUserName, []byte("testUserName")),
- // NewField(fieldUserPassword, []byte(NegatedUserString([]byte("testPw")))),
- // NewField(fieldUserAccess, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}),
- // ),
- // want: &Transaction{
- // Fields: []Field{},
- // },
- // },
- // {
- // name: "a newUser request from a user without the required access",
- // mockHandler: func() *mockClientHandler {
- // mh := mockClientHandler{}
- // mh.On("Handle", mock.AnythingOfType("*hotline.Client"), mock.MatchedBy(func(t *Transaction) bool {
- // if !bytes.Equal(t.GetField(fieldError).Data, []byte("You are not allowed to create new accounts.")) {
- // return false
- // }
- // return bytes.Equal(t.Type, []byte{0x01, 0x5e})
- // })).Return(
- // []Transaction{}, nil,
- // )
- // return &mh
- // }(),
- // teardown: func() {
- // _ = srv.DeleteUser("testUser")
- // },
- // account: Account{
- // Login: "test",
- // Name: "unnamed",
- // Password: "test",
- // Access: &[]byte{0, 0, 0, 0, 0, 0, 0, 0},
- // },
- // request: NewTransaction(
- // tranNewUser, nil,
- // NewField(fieldUserLogin, []byte(NegatedUserString([]byte("testUser")))),
- // NewField(fieldUserName, []byte("testUserName")),
- // NewField(fieldUserPassword, []byte(NegatedUserString([]byte("testPw")))),
- // NewField(fieldUserAccess, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}),
- // ),
- // },
- // {
- // name: "when user does not have required permission",
- // mockHandler: func() map[int]*mockClientHandler {
- // mockHandlers := make(map[int]*mockClientHandler)
- //
- // mh := mockClientHandler{}
- // mh.On("Handle", mock.AnythingOfType("*hotline.Client"), mock.MatchedBy(func(t *Transaction) bool {
- // return t.equal(Transaction{
- // Type: []byte{0x01, 0x5e},
- // IsReply: 1,
- // ErrorCode: []byte{0, 0, 0, 1},
- // Fields: []Field{
- // NewField(fieldError, []byte("You are not allowed to create new accounts.")),
- // },
- // })
- // })).Return(
- // []Transaction{}, nil,
- // )
- // mockHandlers[tranNewUser] = &mh
- //
- // return mockHandlers
- // }(),
- //
- // teardown: func() {
- // _ = srv.DeleteUser("testUser")
- // },
- // account: Account{
- // Login: "test",
- // Name: "unnamed",
- // Password: "test",
- // Access: &[]byte{0, 0, 0, 0, 0, 0, 0, 0},
- // },
- // request: NewTransaction(
- // tranNewUser, nil,
- // NewField(fieldUserLogin, []byte(NegatedUserString([]byte("testUser")))),
- // NewField(fieldUserName, []byte("testUserName")),
- // NewField(fieldUserPassword, []byte(NegatedUserString([]byte("testPw")))),
- // NewField(fieldUserAccess, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}),
- // ),
- // },
-
- // {
- // name: "a request to create a user that already exists",
- // setup: func() {
- //
- // },
- // teardown: func() {
- // _ = srv.DeleteUser("testUser")
- // },
- // account: Account{
- // Login: "test",
- // Name: "unnamed",
- // Password: "test",
- // Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
- // },
- // request: NewTransaction(
- // tranNewUser, nil,
- // NewField(fieldUserLogin, []byte(NegatedUserString([]byte("guest")))),
- // NewField(fieldUserName, []byte("testUserName")),
- // NewField(fieldUserPassword, []byte(NegatedUserString([]byte("testPw")))),
- // NewField(fieldUserAccess, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}),
- // ),
- // want: &Transaction{
- // Fields: []Field{
- // NewField(fieldError, []byte("Cannot create account guest because there is already an account with that login.")),
- // },
- // },
- // },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- test.Setup(srv)
-
- // move to Setup?
- c := NewClient(test.account.Name, NewTestLogger())
- err := c.JoinServer(fmt.Sprintf(":%v", srv.APIPort()), test.account.Login, test.account.Password)
- if err != nil {
- t.Errorf("login failed: %v", err)
- }
- // end move to Setup??
-
- for key, value := range test.mockHandler {
- c.Handlers[uint16(key)] = value
- }
-
- // send test case request
- _ = c.Send(*test.request)
-
- // time.Sleep(1 * time.Second)
- // ===
-
- transactions, _ := readN(c.Connection, 1)
- for _, t := range transactions {
- _ = c.HandleTransaction(&t)
- }
-
- // ===
-
- for _, handler := range test.mockHandler {
- handler.AssertExpectations(t)
- }
-
- test.Teardown(srv)
- })
- }
-}
-
// tranAssertEqual compares equality of transactions slices after stripping out the random ID
func tranAssertEqual(t *testing.T, tran1, tran2 []Transaction) bool {
var newT1 []Transaction
"fmt"
"github.com/jhalter/mobius/concat"
"math/rand"
- "net"
)
const (
}, tranLen, nil
}
-func readN(conn net.Conn, n int) ([]Transaction, error) {
- buf := make([]byte, 1400)
- i := 0
- for {
- readLen, err := conn.Read(buf)
- if err != nil {
- return nil, err
- }
-
- transactions, _, err := readTransactions(buf[:readLen])
- // spew.Fdump(os.Stderr, transactions)
- if err != nil {
- return nil, err
- }
-
- i += len(transactions)
-
- if n == i {
- return transactions, nil
- }
- }
-}
-
func readTransactions(buf []byte) ([]Transaction, int, error) {
var transactions []Transaction
)
type TransactionType struct {
- Access int // Specifies access privilege required to perform the transaction
- DenyMsg string // The error reply message when user does not have access
Handler func(*ClientConn, *Transaction) ([]Transaction, error) // function for handling the transaction type
Name string // Name of transaction as it will appear in logging
RequiredFields []requiredField
Name: "tranNotifyDeleteUser",
},
tranAgreed: {
- Access: accessAlwaysAllow,
Name: "tranAgreed",
Handler: HandleTranAgreed,
},
tranChatSend: {
- Access: accessAlwaysAllow,
- Handler: HandleChatSend,
Name: "tranChatSend",
+ Handler: HandleChatSend,
RequiredFields: []requiredField{
{
ID: fieldData,
},
},
tranDelNewsArt: {
- Access: accessNewsDeleteArt,
- DenyMsg: "You are not allowed to delete news articles.",
Name: "tranDelNewsArt",
Handler: HandleDelNewsArt,
},
tranDelNewsItem: {
- Access: accessAlwaysAllow, // Granular access enforced inside the handler
- // Has multiple access flags: News Delete Folder (37) or News Delete Category (35)
- // TODO: Implement inside the handler
Name: "tranDelNewsItem",
Handler: HandleDelNewsItem,
},
tranDeleteFile: {
- Access: accessAlwaysAllow, // Granular access enforced inside the handler
Name: "tranDeleteFile",
Handler: HandleDeleteFile,
},
tranDeleteUser: {
- Access: accessAlwaysAllow,
Name: "tranDeleteUser",
Handler: HandleDeleteUser,
},
tranDisconnectUser: {
- Access: accessDisconUser,
- DenyMsg: "You are not allowed to disconnect users.",
Name: "tranDisconnectUser",
Handler: HandleDisconnectUser,
},
tranDownloadFile: {
- Access: accessAlwaysAllow,
Name: "tranDownloadFile",
Handler: HandleDownloadFile,
},
tranDownloadFldr: {
- Access: accessDownloadFile, // There is no specific access flag for folder vs file download
- DenyMsg: "You are not allowed to download files.",
Name: "tranDownloadFldr",
Handler: HandleDownloadFolder,
},
tranGetClientInfoText: {
- Access: accessGetClientInfo,
- DenyMsg: "You are not allowed to get client info",
Name: "tranGetClientInfoText",
Handler: HandleGetClientConnInfoText,
},
tranGetFileInfo: {
- Access: accessAlwaysAllow,
Name: "tranGetFileInfo",
Handler: HandleGetFileInfo,
},
tranGetFileNameList: {
- Access: accessAlwaysAllow,
Name: "tranGetFileNameList",
Handler: HandleGetFileNameList,
},
tranGetMsgs: {
- Access: accessAlwaysAllow,
Name: "tranGetMsgs",
Handler: HandleGetMsgs,
},
tranGetNewsArtData: {
- Access: accessNewsReadArt,
- DenyMsg: "You are not allowed to read news.",
Name: "tranGetNewsArtData",
Handler: HandleGetNewsArtData,
},
tranGetNewsArtNameList: {
- Access: accessNewsReadArt,
- DenyMsg: "You are not allowed to read news.",
Name: "tranGetNewsArtNameList",
Handler: HandleGetNewsArtNameList,
},
tranGetNewsCatNameList: {
- Access: accessNewsReadArt,
- DenyMsg: "You are not allowed to read news.",
Name: "tranGetNewsCatNameList",
Handler: HandleGetNewsCatNameList,
},
tranGetUser: {
- Access: accessAlwaysAllow,
Name: "tranGetUser",
Handler: HandleGetUser,
},
tranGetUserNameList: {
- Access: accessAlwaysAllow,
Name: "tranHandleGetUserNameList",
Handler: HandleGetUserNameList,
},
tranInviteNewChat: {
- Access: accessOpenChat,
- DenyMsg: "You are not allowed to request private chat.",
Name: "tranInviteNewChat",
Handler: HandleInviteNewChat,
},
tranInviteToChat: {
- Access: accessOpenChat,
- DenyMsg: "You are not allowed to request private chat.",
Name: "tranInviteToChat",
Handler: HandleInviteToChat,
},
tranJoinChat: {
- Access: accessAlwaysAllow,
Name: "tranJoinChat",
Handler: HandleJoinChat,
},
tranKeepAlive: {
- Access: accessAlwaysAllow,
Name: "tranKeepAlive",
Handler: HandleKeepAlive,
},
tranLeaveChat: {
- Access: accessAlwaysAllow,
Name: "tranJoinChat",
Handler: HandleLeaveChat,
},
tranListUsers: {
- Access: accessAlwaysAllow,
Name: "tranListUsers",
Handler: HandleListUsers,
},
tranMoveFile: {
- Access: accessMoveFile,
- DenyMsg: "You are not allowed to move files.",
Name: "tranMoveFile",
Handler: HandleMoveFile,
},
tranNewFolder: {
- Access: accessCreateFolder,
- DenyMsg: "You are not allow to create folders.",
Name: "tranNewFolder",
Handler: HandleNewFolder,
},
tranNewNewsCat: {
- Access: accessNewsCreateCat,
- DenyMsg: "You are not allowed to create news categories.",
Name: "tranNewNewsCat",
Handler: HandleNewNewsCat,
},
tranNewNewsFldr: {
- Access: accessNewsCreateFldr,
- DenyMsg: "You are not allowed to create news folders.",
Name: "tranNewNewsFldr",
Handler: HandleNewNewsFldr,
},
tranNewUser: {
- Access: accessAlwaysAllow,
Name: "tranNewUser",
Handler: HandleNewUser,
},
tranUpdateUser: {
- Access: accessAlwaysAllow,
Name: "tranUpdateUser",
Handler: HandleUpdateUser,
},
tranOldPostNews: {
- Access: accessNewsPostArt,
- DenyMsg: "You are not allowed to post news.",
Name: "tranOldPostNews",
Handler: HandleTranOldPostNews,
},
tranPostNewsArt: {
- Access: accessNewsPostArt,
- DenyMsg: "You are not allowed to post news articles.",
Name: "tranPostNewsArt",
Handler: HandlePostNewsArt,
},
tranRejectChatInvite: {
- Access: accessAlwaysAllow,
Name: "tranRejectChatInvite",
Handler: HandleRejectChatInvite,
},
tranSendInstantMsg: {
- Access: accessAlwaysAllow,
- // Access: accessSendPrivMsg,
- // DenyMsg: "You are not allowed to send private messages",
Name: "tranSendInstantMsg",
Handler: HandleSendInstantMsg,
RequiredFields: []requiredField{
},
},
tranSetChatSubject: {
- Access: accessAlwaysAllow,
Name: "tranSetChatSubject",
Handler: HandleSetChatSubject,
},
tranMakeFileAlias: {
- Access: accessAlwaysAllow,
Name: "tranMakeFileAlias",
Handler: HandleMakeAlias,
RequiredFields: []requiredField{
},
},
tranSetClientUserInfo: {
- Access: accessAlwaysAllow,
Name: "tranSetClientUserInfo",
Handler: HandleSetClientUserInfo,
},
tranSetFileInfo: {
- Access: accessAlwaysAllow,
Name: "tranSetFileInfo",
Handler: HandleSetFileInfo,
},
tranSetUser: {
- Access: accessModifyUser,
- DenyMsg: "You are not allowed to modify accounts.",
Name: "tranSetUser",
Handler: HandleSetUser,
},
tranUploadFile: {
- Access: accessAlwaysAllow,
Name: "tranUploadFile",
Handler: HandleUploadFile,
},
tranUploadFldr: {
- Access: accessAlwaysAllow,
Name: "tranUploadFldr",
Handler: HandleUploadFolder,
},
tranUserBroadcast: {
- Access: accessBroadcast,
- DenyMsg: "You are not allowed to send broadcast messages.",
Name: "tranUserBroadcast",
Handler: HandleUserBroadcast,
},
}
func HandleNewFolder(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
+ if !authorize(cc.Account.Access, accessCreateFolder) {
+ res = append(res, cc.NewErrReply(t, "You are not allowed to create folders."))
+ return res, err
+ }
newFolderPath := cc.Server.Config.FileRoot
folderName := string(t.GetField(fieldFileName).Data)
}
func HandleSetUser(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
+ if !authorize(cc.Account.Access, accessModifyUser) {
+ res = append(res, cc.NewErrReply(t, "You are not allowed to modify accounts."))
+ return res, err
+ }
+
login := DecodeUserString(t.GetField(fieldUserLogin).Data)
userName := string(t.GetField(fieldUserName).Data)
// HandleUserBroadcast sends an Administrator Message to all connected clients of the server
func HandleUserBroadcast(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
+ if !authorize(cc.Account.Access, accessBroadcast) {
+ res = append(res, cc.NewErrReply(t, "You are not allowed to send broadcast messages."))
+ return res, err
+ }
+
cc.sendAll(
tranServerMsg,
NewField(fieldData, t.GetField(tranGetMsgs).Data),
}
func HandleGetClientConnInfoText(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
+ if !authorize(cc.Account.Access, accessGetClientInfo) {
+ res = append(res, cc.NewErrReply(t, "You are not allowed to get client info"))
+ return res, err
+ }
+
clientID, _ := byteToInt(t.GetField(fieldUserID).Data)
clientConn := cc.Server.Clients[uint16(clientID)]
clientConn.UserName,
clientConn.Account.Name,
clientConn.Account.Login,
- clientConn.Connection.RemoteAddr().String(),
+ clientConn.RemoteAddr,
activeDownloadList,
)
template = strings.Replace(template, "\n", "\r", -1)
// Fields used in this request:
// 101 Data
func HandleTranOldPostNews(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
+ if !authorize(cc.Account.Access, accessNewsPostArt) {
+ res = append(res, cc.NewErrReply(t, "You are not allowed to post news."))
+ return res, err
+ }
+
cc.Server.flatNewsMux.Lock()
defer cc.Server.flatNewsMux.Unlock()
}
func HandleDisconnectUser(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
+ if !authorize(cc.Account.Access, accessDisconUser) {
+ res = append(res, cc.NewErrReply(t, "You are not allowed to disconnect users."))
+ return res, err
+ }
+
clientConn := cc.Server.Clients[binary.BigEndian.Uint16(t.GetField(fieldUserID).Data)]
if authorize(clientConn.Account.Access, accessCannotBeDiscon) {
return res, err
}
+// HandleGetNewsCatNameList returns a list of news categories for a path
+// Fields used in the request:
+// 325 News path (Optional)
func HandleGetNewsCatNameList(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
- // Fields used in the request:
- // 325 News path (Optional)
+ if !authorize(cc.Account.Access, accessNewsReadArt) {
+ res = append(res, cc.NewErrReply(t, "You are not allowed to read news."))
+ return res, err
+ }
newsPath := t.GetField(fieldNewsPath).Data
cc.Server.Logger.Infow("NewsPath: ", "np", string(newsPath))
}
func HandleNewNewsCat(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
+ if !authorize(cc.Account.Access, accessNewsCreateCat) {
+ res = append(res, cc.NewErrReply(t, "You are not allowed to create news categories."))
+ return res, err
+ }
+
name := string(t.GetField(fieldNewsCatName).Data)
pathStrs := ReadNewsPath(t.GetField(fieldNewsPath).Data)
return res, err
}
+// Fields used in the request:
+// 322 News category name
+// 325 News path
func HandleNewNewsFldr(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
- // Fields used in the request:
- // 322 News category name
- // 325 News path
+ if !authorize(cc.Account.Access, accessNewsCreateFldr) {
+ res = append(res, cc.NewErrReply(t, "You are not allowed to create news folders."))
+ return res, err
+ }
+
name := string(t.GetField(fieldFileName).Data)
pathStrs := ReadNewsPath(t.GetField(fieldNewsPath).Data)
// Reply fields:
// 321 News article list data Optional
func HandleGetNewsArtNameList(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
+ if !authorize(cc.Account.Access, accessNewsReadArt) {
+ res = append(res, cc.NewErrReply(t, "You are not allowed to read news."))
+ return res, err
+ }
pathStrs := ReadNewsPath(t.GetField(fieldNewsPath).Data)
var cat NewsCategoryListData15
}
func HandleGetNewsArtData(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
+ if !authorize(cc.Account.Access, accessNewsReadArt) {
+ res = append(res, cc.NewErrReply(t, "You are not allowed to read news."))
+ return res, err
+ }
+
// Request fields
// 325 News fp
// 326 News article ID
}
func HandleDelNewsItem(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
- // Access: News Delete Folder (37) or News Delete Category (35)
+ // Has multiple access flags: News Delete Folder (37) or News Delete Category (35)
+ // TODO: Implement
pathStrs := ReadNewsPath(t.GetField(fieldNewsPath).Data)
}
func HandleDelNewsArt(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
+ if !authorize(cc.Account.Access, accessNewsDeleteArt) {
+ res = append(res, cc.NewErrReply(t, "You are not allowed to delete news articles."))
+ return res, err
+ }
+
// Request Fields
// 325 News path
// 326 News article ID
return res, err
}
+// Request fields
+// 325 News path
+// 326 News article ID ID of the parent article?
+// 328 News article title
+// 334 News article flags
+// 327 News article data flavor Currently “text/plain”
+// 333 News article data
func HandlePostNewsArt(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
- // Request fields
- // 325 News path
- // 326 News article ID ID of the parent article?
- // 328 News article title
- // 334 News article flags
- // 327 News article data flavor Currently “text/plain”
- // 333 News article data
+ if !authorize(cc.Account.Access, accessNewsPostArt) {
+ res = append(res, cc.NewErrReply(t, "You are not allowed to post news articles."))
+ return res, err
+ }
pathStrs := ReadNewsPath(t.GetField(fieldNewsPath).Data)
cats := cc.Server.GetNewsCatByPath(pathStrs[:len(pathStrs)-1])
if resumeData != nil {
var frd FileResumeData
- frd.UnmarshalBinary(t.GetField(fieldFileResumeData).Data)
+ if err := frd.UnmarshalBinary(t.GetField(fieldFileResumeData).Data); err != nil {
+ return res, err
+ }
ft.fileResumeData = &frd
}
}
// Download all files from the specified folder and sub-folders
-// response example
-//
-// 00
-// 01
-// 00 00
-// 00 00 00 11
-// 00 00 00 00
-// 00 00 00 18
-// 00 00 00 18
-//
-// 00 03
-//
-// 00 6c // transfer size
-// 00 04 // len
-// 00 0f d5 ae
-//
-// 00 dc // field Folder item count
-// 00 02 // len
-// 00 02
-//
-// 00 6b // ref number
-// 00 04 // len
-// 00 03 64 b1
func HandleDownloadFolder(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
+ if !authorize(cc.Account.Access, accessDownloadFile) {
+ res = append(res, cc.NewErrReply(t, "You are not allowed to download folders."))
+ return res, err
+ }
+
transactionRef := cc.Server.NewTransactionRef()
data := binary.BigEndian.Uint32(transactionRef)
// HandleInviteNewChat invites users to new private chat
func HandleInviteNewChat(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
+ if !authorize(cc.Account.Access, accessOpenChat) {
+ res = append(res, cc.NewErrReply(t, "You are not allowed to request private chat."))
+ return res, err
+ }
+
// Client to Invite
targetID := t.GetField(fieldUserID).Data
newChatID := cc.Server.NewPrivateChat(cc)
}
func HandleInviteToChat(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
+ if !authorize(cc.Account.Access, accessOpenChat) {
+ res = append(res, cc.NewErrReply(t, "You are not allowed to request private chat."))
+ return res, err
+ }
+
// Client to Invite
targetID := t.GetField(fieldUserID).Data
chatID := t.GetField(fieldChatID).Data
wantRes []Transaction
wantErr bool
}{
+ {
+ name: "without required permission",
+ setup: func() {},
+ args: args{
+ cc: &ClientConn{
+ Account: &Account{
+ Access: func() *[]byte {
+ var bits accessBitmap
+ access := bits[:]
+ return &access
+ }(),
+ },
+ },
+ t: NewTransaction(
+ accessCreateFolder,
+ &[]byte{0, 0},
+ ),
+ },
+ wantRes: []Transaction{
+ {
+ Flags: 0x00,
+ IsReply: 0x01,
+ Type: []byte{0, 0x00},
+ ID: []byte{0x9a, 0xcb, 0x04, 0x42},
+ ErrorCode: []byte{0, 0, 0, 1},
+ Fields: []Field{
+ NewField(fieldError, []byte("You are not allowed to create folders.")),
+ },
+ },
+ },
+ wantErr: false,
+ },
{
name: "when path is nested",
args: args{
cc: &ClientConn{
+ Account: &Account{
+ Access: func() *[]byte {
+ var bits accessBitmap
+ bits.Set(accessCreateFolder)
+ access := bits[:]
+ return &access
+ }(),
+ },
ID: &[]byte{0, 1},
Server: &Server{
Config: &Config{
name: "when path is not nested",
args: args{
cc: &ClientConn{
+ Account: &Account{
+ Access: func() *[]byte {
+ var bits accessBitmap
+ bits.Set(accessCreateFolder)
+ access := bits[:]
+ return &access
+ }(),
+ },
ID: &[]byte{0, 1},
Server: &Server{
Config: &Config{
name: "when UnmarshalBinary returns an err",
args: args{
cc: &ClientConn{
+ Account: &Account{
+ Access: func() *[]byte {
+ var bits accessBitmap
+ bits.Set(accessCreateFolder)
+ access := bits[:]
+ return &access
+ }(),
+ },
ID: &[]byte{0, 1},
Server: &Server{
Config: &Config{
name: "fieldFileName does not allow directory traversal",
args: args{
cc: &ClientConn{
+ Account: &Account{
+ Access: func() *[]byte {
+ var bits accessBitmap
+ bits.Set(accessCreateFolder)
+ access := bits[:]
+ return &access
+ }(),
+ },
ID: &[]byte{0, 1},
Server: &Server{
Config: &Config{
name: "fieldFilePath does not allow directory traversal",
args: args{
cc: &ClientConn{
+ Account: &Account{
+ Access: func() *[]byte {
+ var bits accessBitmap
+ bits.Set(accessCreateFolder)
+ access := bits[:]
+ return &access
+ }(),
+ },
ID: &[]byte{0, 1},
Server: &Server{
Config: &Config{
t.Errorf("HandleNewFolder() error = %v, wantErr %v", err, tt.wantErr)
return
}
+
if !tranAssertEqual(t, tt.wantRes, gotRes) {
t.Errorf("HandleNewFolder() gotRes = %v, want %v", gotRes, tt.wantRes)
}
})
}
}
+
+func TestHandleDelNewsArt(t *testing.T) {
+ type args struct {
+ cc *ClientConn
+ t *Transaction
+ }
+ tests := []struct {
+ name string
+ args args
+ wantRes []Transaction
+ wantErr assert.ErrorAssertionFunc
+ }{
+ {
+ name: "without required permission",
+ args: args{
+ cc: &ClientConn{
+ Account: &Account{
+ Access: func() *[]byte {
+ var bits accessBitmap
+ access := bits[:]
+ return &access
+ }(),
+ },
+ },
+ t: NewTransaction(
+ tranDelNewsArt,
+ &[]byte{0, 0},
+ ),
+ },
+ wantRes: []Transaction{
+ {
+ Flags: 0x00,
+ IsReply: 0x01,
+ Type: []byte{0, 0x00},
+ ID: []byte{0x9a, 0xcb, 0x04, 0x42},
+ ErrorCode: []byte{0, 0, 0, 1},
+ Fields: []Field{
+ NewField(fieldError, []byte("You are not allowed to delete news articles.")),
+ },
+ },
+ },
+ wantErr: assert.NoError,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ gotRes, err := HandleDelNewsArt(tt.args.cc, tt.args.t)
+ if !tt.wantErr(t, err, fmt.Sprintf("HandleDelNewsArt(%v, %v)", tt.args.cc, tt.args.t)) {
+ return
+ }
+ tranAssertEqual(t, tt.wantRes, gotRes)
+ })
+ }
+}
+
+func TestHandleDisconnectUser(t *testing.T) {
+ type args struct {
+ cc *ClientConn
+ t *Transaction
+ }
+ tests := []struct {
+ name string
+ args args
+ wantRes []Transaction
+ wantErr assert.ErrorAssertionFunc
+ }{
+ {
+ name: "without required permission",
+ args: args{
+ cc: &ClientConn{
+ Account: &Account{
+ Access: func() *[]byte {
+ var bits accessBitmap
+ access := bits[:]
+ return &access
+ }(),
+ },
+ },
+ t: NewTransaction(
+ tranDelNewsArt,
+ &[]byte{0, 0},
+ ),
+ },
+ wantRes: []Transaction{
+ {
+ Flags: 0x00,
+ IsReply: 0x01,
+ Type: []byte{0, 0x00},
+ ID: []byte{0x9a, 0xcb, 0x04, 0x42},
+ ErrorCode: []byte{0, 0, 0, 1},
+ Fields: []Field{
+ NewField(fieldError, []byte("You are not allowed to disconnect users.")),
+ },
+ },
+ },
+ wantErr: assert.NoError,
+ },
+ {
+ name: "when target user has 'cannot be disconnected' priv",
+ args: args{
+ cc: &ClientConn{
+ Server: &Server{
+ Clients: map[uint16]*ClientConn{
+ uint16(1): {
+ Account: &Account{
+ Login: "unnamed",
+ Access: func() *[]byte {
+ var bits accessBitmap
+ bits.Set(accessCannotBeDiscon)
+ access := bits[:]
+ return &access
+ }(),
+ },
+ },
+ },
+ },
+ Account: &Account{
+ Access: func() *[]byte {
+ var bits accessBitmap
+ bits.Set(accessDisconUser)
+ access := bits[:]
+ return &access
+ }(),
+ },
+ },
+ t: NewTransaction(
+ tranDelNewsArt,
+ &[]byte{0, 0},
+ NewField(fieldUserID, []byte{0, 1}),
+ ),
+ },
+ wantRes: []Transaction{
+ {
+ Flags: 0x00,
+ IsReply: 0x01,
+ Type: []byte{0, 0x00},
+ ID: []byte{0x9a, 0xcb, 0x04, 0x42},
+ ErrorCode: []byte{0, 0, 0, 1},
+ Fields: []Field{
+ NewField(fieldError, []byte("unnamed is not allowed to be disconnected.")),
+ },
+ },
+ },
+ wantErr: assert.NoError,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ gotRes, err := HandleDisconnectUser(tt.args.cc, tt.args.t)
+ if !tt.wantErr(t, err, fmt.Sprintf("HandleDisconnectUser(%v, %v)", tt.args.cc, tt.args.t)) {
+ return
+ }
+ tranAssertEqual(t, tt.wantRes, gotRes)
+ })
+ }
+}
import (
"fmt"
- "github.com/davecgh/go-spew/spew"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
"gopkg.in/yaml.v3"
func (ui *UI) getTrackerList() *tview.List {
listing, err := GetListing(ui.HLClient.pref.Tracker)
if err != nil {
- spew.Dump(err)
+ // TODO
}
list := tview.NewList()