X-Git-Url: https://git.r.bdr.sh/rbdr/mobius/blobdiff_plain/2e1aec0fcfd412cff22cc4fc996973e403589ad8..a6216dd89252fa01dc176f98f1e4ecfd3f637566:/hotline/transaction_handlers.go diff --git a/hotline/transaction_handlers.go b/hotline/transaction_handlers.go index 931340e..3accf2c 100644 --- a/hotline/transaction_handlers.go +++ b/hotline/transaction_handlers.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "gopkg.in/yaml.v3" + "io" "math/big" "os" "path" @@ -337,7 +338,7 @@ func HandleSendInstantMsg(cc *ClientConn, t *Transaction) (res []Transaction, er // Check if target user has "Refuse private messages" flag flagBitmap := big.NewInt(int64(binary.BigEndian.Uint16(otherClient.Flags))) - if flagBitmap.Bit(UserFlagRefusePChat) == 1 { + if flagBitmap.Bit(UserFlagRefusePM) == 1 { res = append(res, *NewTransaction( TranServerMsg, @@ -390,16 +391,26 @@ func HandleGetFileInfo(cc *ClientConn, t *Transaction) (res []Transaction, err e return res, fmt.Errorf("invalid filepath encoding: %w", err) } - res = append(res, cc.NewReply(t, + fields := []Field{ NewField(FieldFileName, []byte(encodedName)), NewField(FieldFileTypeString, fw.ffo.FlatFileInformationFork.friendlyType()), NewField(FieldFileCreatorString, fw.ffo.FlatFileInformationFork.friendlyCreator()), - NewField(FieldFileComment, fw.ffo.FlatFileInformationFork.Comment), NewField(FieldFileType, fw.ffo.FlatFileInformationFork.TypeSignature), NewField(FieldFileCreateDate, fw.ffo.FlatFileInformationFork.CreateDate), NewField(FieldFileModifyDate, fw.ffo.FlatFileInformationFork.ModifyDate), - NewField(FieldFileSize, fw.totalSize()), - )) + } + + // Include the optional FileComment field if there is a comment. + if len(fw.ffo.FlatFileInformationFork.Comment) != 0 { + fields = append(fields, NewField(FieldFileComment, fw.ffo.FlatFileInformationFork.Comment)) + } + + // Include the FileSize field for files. + if !bytes.Equal(fw.ffo.FlatFileInformationFork.TypeSignature, []byte{0x66, 0x6c, 0x64, 0x72}) { + fields = append(fields, NewField(FieldFileSize, fw.totalSize())) + } + + res = append(res, cc.NewReply(t, fields...)) return res, err } @@ -449,7 +460,7 @@ func HandleSetFileInfo(cc *ClientConn, t *Transaction) (res []Transaction, err e if err != nil { return res, err } - _, err = w.Write(hlFile.ffo.FlatFileInformationFork.MarshalBinary()) + _, err = io.Copy(w, &hlFile.ffo.FlatFileInformationFork) if err != nil { return res, err } @@ -563,7 +574,7 @@ func HandleMoveFile(cc *ClientConn, t *Transaction) (res []Transaction, err erro return res, err } - cc.logger.Infow("Move file", "src", filePath+"/"+fileName, "dst", fileNewPath+"/"+fileName) + cc.logger.Info("Move file", "src", filePath+"/"+fileName, "dst", fileNewPath+"/"+fileName) hlFile, err := newFileWrapper(cc.Server.FS, filePath, 0) if err != nil { @@ -647,12 +658,15 @@ func HandleSetUser(cc *ClientConn, t *Transaction) (res []Transaction, err error return res, err } - login := decodeString(t.GetField(FieldUserLogin).Data) + login := string(encodeString(t.GetField(FieldUserLogin).Data)) userName := string(t.GetField(FieldUserName).Data) newAccessLvl := t.GetField(FieldUserAccess).Data account := cc.Server.Accounts[login] + if account == nil { + return append(res, cc.NewErrReply(t, "Account not found.")), nil + } account.Name = userName copy(account.Access[:], newAccessLvl) @@ -661,7 +675,8 @@ func HandleSetUser(cc *ClientConn, t *Transaction) (res []Transaction, err error if t.GetField(FieldUserPassword).Data == nil { account.Password = hashAndSalt([]byte("")) } - if len(t.GetField(FieldUserPassword).Data) > 1 { + + if !bytes.Equal([]byte{0}, t.GetField(FieldUserPassword).Data) { account.Password = hashAndSalt(t.GetField(FieldUserPassword).Data) } @@ -733,13 +748,12 @@ func HandleListUsers(cc *ClientConn, t *Transaction) (res []Transaction, err err var userFields []Field for _, acc := range cc.Server.Accounts { - b := make([]byte, 0, 100) - n, err := acc.Read(b) + b, err := io.ReadAll(acc) if err != nil { return res, err } - userFields = append(userFields, NewField(FieldData, b[:n])) + userFields = append(userFields, NewField(FieldData, b)) } res = append(res, cc.NewReply(t, userFields...)) @@ -762,36 +776,63 @@ func HandleUpdateUser(cc *ClientConn, t *Transaction) (res []Transaction, err er return res, err } + // If there's only one subfield, that indicates this is a delete operation for the login in FieldData if len(subFields) == 1 { - login := decodeString(getField(FieldData, &subFields).Data) - cc.logger.Infow("DeleteUser", "login", login) - if !cc.Authorize(accessDeleteUser) { res = append(res, cc.NewErrReply(t, "You are not allowed to delete accounts.")) return res, err } + login := string(encodeString(getField(FieldData, &subFields).Data)) + cc.logger.Info("DeleteUser", "login", login) + if err := cc.Server.DeleteUser(login); err != nil { return res, err } continue } - login := decodeString(getField(FieldUserLogin, &subFields).Data) + // login of the account to update + var accountToUpdate, loginToRename string - // check if the login dataFile; if so, we know we are updating an existing user - if acc, ok := cc.Server.Accounts[login]; ok { - cc.logger.Infow("UpdateUser", "login", login) + // If FieldData is included, this is a rename operation where FieldData contains the login of the existing + // account and FieldUserLogin contains the new login. + if getField(FieldData, &subFields) != nil { + loginToRename = string(encodeString(getField(FieldData, &subFields).Data)) + } + userLogin := string(encodeString(getField(FieldUserLogin, &subFields).Data)) + if loginToRename != "" { + accountToUpdate = loginToRename + } else { + accountToUpdate = userLogin + } - // account dataFile, so this is an update action + // Check if accountToUpdate has an existing account. If so, we know we are updating an existing user. + if acc, ok := cc.Server.Accounts[accountToUpdate]; ok { + if loginToRename != "" { + cc.logger.Info("RenameUser", "prevLogin", accountToUpdate, "newLogin", userLogin) + } else { + cc.logger.Info("UpdateUser", "login", accountToUpdate) + } + + // account exists, so this is an update action if !cc.Authorize(accessModifyUser) { res = append(res, cc.NewErrReply(t, "You are not allowed to modify accounts.")) - return res, err + return res, nil } + // This part is a bit tricky. There are three possibilities: + // 1) The transaction is intended to update the password. + // In this case, FieldUserPassword is sent with the new password. + // 2) The transaction is intended to remove the password. + // In this case, FieldUserPassword is not sent. + // 3) The transaction updates the users access bits, but not the password. + // In this case, FieldUserPassword is sent with zero as the only byte. if getField(FieldUserPassword, &subFields) != nil { newPass := getField(FieldUserPassword, &subFields).Data - acc.Password = hashAndSalt(newPass) + if !bytes.Equal([]byte{0}, newPass) { + acc.Password = hashAndSalt(newPass) + } } else { acc.Password = hashAndSalt([]byte("")) } @@ -801,8 +842,8 @@ func HandleUpdateUser(cc *ClientConn, t *Transaction) (res []Transaction, err er } err = cc.Server.UpdateUser( - decodeString(getField(FieldData, &subFields).Data), - decodeString(getField(FieldUserLogin, &subFields).Data), + string(encodeString(getField(FieldData, &subFields).Data)), + string(encodeString(getField(FieldUserLogin, &subFields).Data)), string(getField(FieldUserName, &subFields).Data), acc.Password, acc.Access, @@ -811,13 +852,13 @@ func HandleUpdateUser(cc *ClientConn, t *Transaction) (res []Transaction, err er return res, err } } else { - cc.logger.Infow("CreateUser", "login", login) - if !cc.Authorize(accessCreateUser) { res = append(res, cc.NewErrReply(t, "You are not allowed to create new accounts.")) - return res, err + return res, nil } + cc.logger.Info("CreateUser", "login", userLogin) + newAccess := accessBitmap{} copy(newAccess[:], getField(FieldUserAccess, &subFields).Data) @@ -825,14 +866,14 @@ func HandleUpdateUser(cc *ClientConn, t *Transaction) (res []Transaction, err er for i := 0; i < 64; i++ { if newAccess.IsSet(i) { if !cc.Authorize(i) { - return append(res, cc.NewErrReply(t, "Cannot create account with more access than yourself.")), err + return append(res, cc.NewErrReply(t, "Cannot create account with more access than yourself.")), nil } } } - err := cc.Server.NewUser(login, string(getField(FieldUserName, &subFields).Data), string(getField(FieldUserPassword, &subFields).Data), newAccess) + err = cc.Server.NewUser(userLogin, string(getField(FieldUserName, &subFields).Data), string(getField(FieldUserPassword, &subFields).Data), newAccess) if err != nil { - return []Transaction{}, err + return append(res, cc.NewErrReply(t, "Cannot create account because there is already an account with that login.")), nil } } } @@ -848,7 +889,7 @@ func HandleNewUser(cc *ClientConn, t *Transaction) (res []Transaction, err error return res, err } - login := decodeString(t.GetField(FieldUserLogin).Data) + login := string(encodeString(t.GetField(FieldUserLogin).Data)) // If the account already dataFile, reply with an error if _, ok := cc.Server.Accounts[login]; ok { @@ -870,7 +911,8 @@ func HandleNewUser(cc *ClientConn, t *Transaction) (res []Transaction, err error } if err := cc.Server.NewUser(login, string(t.GetField(FieldUserName).Data), string(t.GetField(FieldUserPassword).Data), newAccess); err != nil { - return []Transaction{}, err + res = append(res, cc.NewErrReply(t, "Cannot create account because there is already an account with that login.")) + return res, err } res = append(res, cc.NewReply(t)) @@ -880,11 +922,10 @@ func HandleNewUser(cc *ClientConn, t *Transaction) (res []Transaction, err error func HandleDeleteUser(cc *ClientConn, t *Transaction) (res []Transaction, err error) { if !cc.Authorize(accessDeleteUser) { res = append(res, cc.NewErrReply(t, "You are not allowed to delete accounts.")) - return res, err + return res, nil } - // TODO: Handle case where account doesn't exist; e.g. delete race condition - login := decodeString(t.GetField(FieldUserLogin).Data) + login := string(encodeString(t.GetField(FieldUserLogin).Data)) if err := cc.Server.DeleteUser(login); err != nil { return res, err @@ -957,7 +998,7 @@ func HandleTranAgreed(cc *ClientConn, t *Transaction) (res []Transaction, err er cc.Icon = t.GetField(FieldUserIconID).Data cc.logger = cc.logger.With("name", string(cc.UserName)) - cc.logger.Infow("Login successful", "clientVersion", fmt.Sprintf("%v", func() int { i, _ := byteToInt(cc.Version); return i }())) + cc.logger.Info("Login successful", "clientVersion", fmt.Sprintf("%v", func() int { i, _ := byteToInt(cc.Version); return i }())) options := t.GetField(FieldOptions).Data optBitmap := big.NewInt(int64(binary.BigEndian.Uint16(options))) @@ -1066,7 +1107,7 @@ func HandleDisconnectUser(cc *ClientConn, t *Transaction) (res []Transaction, er switch t.GetField(FieldOptions).Data[1] { case 1: // send message: "You are temporarily banned on this server" - cc.logger.Infow("Disconnect & temporarily ban " + string(clientConn.UserName)) + cc.logger.Info("Disconnect & temporarily ban " + string(clientConn.UserName)) res = append(res, *NewTransaction( TranServerMsg, @@ -1079,7 +1120,7 @@ func HandleDisconnectUser(cc *ClientConn, t *Transaction) (res []Transaction, er cc.Server.banList[strings.Split(clientConn.RemoteAddr, ":")[0]] = &banUntil case 2: // send message: "You are permanently banned on this server" - cc.logger.Infow("Disconnect & ban " + string(clientConn.UserName)) + cc.logger.Info("Disconnect & ban " + string(clientConn.UserName)) res = append(res, *NewTransaction( TranServerMsg, @@ -1153,7 +1194,7 @@ func HandleNewNewsCat(cc *ClientConn, t *Transaction) (res []Transaction, err er cats := cc.Server.GetNewsCatByPath(pathStrs) cats[name] = NewsCategoryListData15{ Name: name, - Type: []byte{0, 3}, + Type: [2]byte{0, 3}, Articles: map[uint32]*NewsArtData{}, SubCats: make(map[string]NewsCategoryListData15), } @@ -1177,12 +1218,10 @@ func HandleNewNewsFldr(cc *ClientConn, t *Transaction) (res []Transaction, err e name := string(t.GetField(FieldFileName).Data) pathStrs := ReadNewsPath(t.GetField(FieldNewsPath).Data) - cc.logger.Infof("Creating new news folder %s", name) - cats := cc.Server.GetNewsCatByPath(pathStrs) cats[name] = NewsCategoryListData15{ Name: name, - Type: []byte{0, 2}, + Type: [2]byte{0, 2}, Articles: map[uint32]*NewsArtData{}, SubCats: make(map[string]NewsCategoryListData15), } @@ -1217,7 +1256,12 @@ func HandleGetNewsArtNameList(cc *ClientConn, t *Transaction) (res []Transaction nald := cat.GetNewsArtListData() - res = append(res, cc.NewReply(t, NewField(FieldNewsArtListData, nald.Payload()))) + b, err := io.ReadAll(&nald) + if err != nil { + + } + + res = append(res, cc.NewReply(t, NewField(FieldNewsArtListData, b))) return res, err } @@ -1296,7 +1340,7 @@ func HandleDelNewsItem(cc *ClientConn, t *Transaction) (res []Transaction, err e } } - if bytes.Equal(cats[delName].Type, []byte{0, 3}) { + if cats[delName].Type == [2]byte{0, 3} { if !cc.Authorize(accessNewsDeleteCat) { return append(res, cc.NewErrReply(t, "You are not allowed to delete news categories.")), nil } @@ -1877,14 +1921,17 @@ func HandleJoinChat(cc *ClientConn, t *Transaction) (res []Transaction, err erro replyFields := []Field{NewField(FieldChatSubject, []byte(privChat.Subject))} for _, c := range sortedClients(privChat.ClientConn) { - user := User{ + + b, err := io.ReadAll(&User{ ID: *c.ID, Icon: c.Icon, Flags: c.Flags, Name: string(c.UserName), + }) + if err != nil { + return res, nil } - - replyFields = append(replyFields, NewField(FieldUsernameWithInfo, user.Payload())) + replyFields = append(replyFields, NewField(FieldUsernameWithInfo, b)) } res = append(res, cc.NewReply(t, replyFields...)) @@ -1975,7 +2022,7 @@ func HandleMakeAlias(cc *ClientConn, t *Transaction) (res []Transaction, err err return res, err } - cc.logger.Debugw("Make alias", "src", fullFilePath, "dst", fullNewFilePath) + cc.logger.Debug("Make alias", "src", fullFilePath, "dst", fullNewFilePath) if err := cc.Server.FS.Symlink(fullFilePath, fullNewFilePath); err != nil { res = append(res, cc.NewErrReply(t, "Error creating alias"))