X-Git-Url: https://git.r.bdr.sh/rbdr/mobius/blobdiff_plain/16a4ad707a05df25c9d12b8cc89fb3a9e3be0dba..93df4153a2028e7c6ffee0d005f58db8cec9917a:/hotline/transaction_handlers.go diff --git a/hotline/transaction_handlers.go b/hotline/transaction_handlers.go index ec7910a..9e524e8 100644 --- a/hotline/transaction_handlers.go +++ b/hotline/transaction_handlers.go @@ -210,6 +210,11 @@ var TransactionHandlers = map[uint16]TransactionType{ Name: "tranNewUser", Handler: HandleNewUser, }, + tranUpdateUser: { + Access: accessAlwaysAllow, + Name: "tranUpdateUser", + Handler: HandleUpdateUser, + }, tranOldPostNews: { Access: accessNewsPostArt, DenyMsg: "You are not allowed to post news.", @@ -414,7 +419,7 @@ func HandleGetFileInfo(cc *ClientConn, t *Transaction) (res []Transaction, err e res = append(res, cc.NewReply(t, NewField(fieldFileName, fileName), - NewField(fieldFileTypeString, ffo.FlatFileInformationFork.TypeSignature), + NewField(fieldFileTypeString, ffo.FlatFileInformationFork.friendlyType()), NewField(fieldFileCreatorString, ffo.FlatFileInformationFork.CreatorSignature), NewField(fieldFileComment, ffo.FlatFileInformationFork.Comment), NewField(fieldFileType, ffo.FlatFileInformationFork.TypeSignature), @@ -615,12 +620,11 @@ func HandleSetUser(cc *ClientConn, t *Transaction) (res []Transaction, err error account.Password = hashAndSalt(t.GetField(fieldUserPassword).Data) } - file := cc.Server.ConfigDir + "Users/" + login + ".yaml" out, err := yaml.Marshal(&account) if err != nil { return res, err } - if err := ioutil.WriteFile(file, out, 0666); err != nil { + if err := os.WriteFile(cc.Server.ConfigDir+"Users/"+login+".yaml", out, 0666); err != nil { return res, err } @@ -683,7 +687,6 @@ func HandleListUsers(cc *ClientConn, t *Transaction) (res []Transaction, err err } var userFields []Field - // TODO: make order deterministic for _, acc := range cc.Server.Accounts { userField := acc.MarshalBinary() userFields = append(userFields, NewField(fieldData, userField)) @@ -693,6 +696,94 @@ func HandleListUsers(cc *ClientConn, t *Transaction) (res []Transaction, err err return res, err } +// HandleUpdateUser is used by the v1.5+ multi-user editor to perform account editing for multiple users at a time. +// An update can be a mix of these actions: +// * Create user +// * Delete user +// * Modify user (including renaming the account login) +// +// The Transaction sent by the client includes one data field per user that was modified. This data field in turn +// contains another data field encoded in its payload with a varying number of sub fields depending on which action is +// performed. This seems to be the only place in the Hotline protocol where a data field contains another data field. +func HandleUpdateUser(cc *ClientConn, t *Transaction) (res []Transaction, err error) { + for _, field := range t.Fields { + subFields, err := ReadFields(field.Data[0:2], field.Data[2:]) + if err != nil { + return res, err + } + + if len(subFields) == 1 { + login := DecodeUserString(getField(fieldData, &subFields).Data) + cc.Server.Logger.Infow("DeleteUser", "login", login) + + if !authorize(cc.Account.Access, accessDeleteUser) { + res = append(res, cc.NewErrReply(t, "You are not allowed to delete accounts.")) + return res, err + } + + if err := cc.Server.DeleteUser(login); err != nil { + return res, err + } + continue + } + + login := DecodeUserString(getField(fieldUserLogin, &subFields).Data) + + // check if the login exists; if so, we know we are updating an existing user + if acc, ok := cc.Server.Accounts[login]; ok { + cc.Server.Logger.Infow("UpdateUser", "login", login) + + // account exists, so this is an update action + if !authorize(cc.Account.Access, accessModifyUser) { + res = append(res, cc.NewErrReply(t, "You are not allowed to modify accounts.")) + return res, err + } + + if getField(fieldUserPassword, &subFields) != nil { + newPass := getField(fieldUserPassword, &subFields).Data + acc.Password = hashAndSalt(newPass) + } else { + acc.Password = hashAndSalt([]byte("")) + } + + if getField(fieldUserAccess, &subFields) != nil { + acc.Access = &getField(fieldUserAccess, &subFields).Data + } + + err = cc.Server.UpdateUser( + DecodeUserString(getField(fieldData, &subFields).Data), + DecodeUserString(getField(fieldUserLogin, &subFields).Data), + string(getField(fieldUserName, &subFields).Data), + acc.Password, + *acc.Access, + ) + if err != nil { + return res, err + } + } else { + cc.Server.Logger.Infow("CreateUser", "login", login) + + if !authorize(cc.Account.Access, accessCreateUser) { + res = append(res, cc.NewErrReply(t, "You are not allowed to create new accounts.")) + return res, err + } + + err := cc.Server.NewUser( + login, + string(getField(fieldUserName, &subFields).Data), + string(getField(fieldUserPassword, &subFields).Data), + getField(fieldUserAccess, &subFields).Data, + ) + if err != nil { + return []Transaction{}, err + } + } + } + + res = append(res, cc.NewReply(t)) + return res, err +} + // HandleNewUser creates a new user account func HandleNewUser(cc *ClientConn, t *Transaction) (res []Transaction, err error) { if !authorize(cc.Account.Access, accessCreateUser) { @@ -1222,8 +1313,6 @@ func HandleDownloadFile(cc *ClientConn, t *Transaction) (res []Transaction, err fileName := t.GetField(fieldFileName).Data filePath := t.GetField(fieldFilePath).Data - // 2 bytes - // transferOptions := t.GetField(fieldFileTransferOptions).Data resumeData := t.GetField(fieldFileResumeData).Data var dataOffset int64 @@ -1262,13 +1351,26 @@ func HandleDownloadFile(cc *ClientConn, t *Transaction) (res []Transaction, err ft.fileResumeData = &frd } + xferSize := ffo.TransferSize() + + // Optional field for when a HL v1.5+ client requests file preview + // Used only for TEXT, JPEG, GIFF, BMP or PICT files + // The value will always be 2 + if t.GetField(fieldFileTransferOptions).Data != nil { + ft.options = t.GetField(fieldFileTransferOptions).Data + xferSize = ffo.FlatFileDataForkHeader.DataSize[:] + } + + cc.Server.mux.Lock() + defer cc.Server.mux.Unlock() cc.Server.FileTransfers[data] = ft + cc.Transfers[FileDownload] = append(cc.Transfers[FileDownload], ft) res = append(res, cc.NewReply(t, NewField(fieldRefNum, transactionRef), NewField(fieldWaitingCount, []byte{0x00, 0x00}), // TODO: Implement waiting count - NewField(fieldTransferSize, ffo.TransferSize()), + NewField(fieldTransferSize, xferSize), NewField(fieldFileSize, ffo.FlatFileDataForkHeader.DataSize[:]), )) @@ -1309,7 +1411,9 @@ func HandleDownloadFolder(cc *ClientConn, t *Transaction) (res []Transaction, er ReferenceNumber: transactionRef, Type: FolderDownload, } + cc.Server.mux.Lock() cc.Server.FileTransfers[data] = fileTransfer + cc.Server.mux.Unlock() cc.Transfers[FolderDownload] = append(cc.Transfers[FolderDownload], fileTransfer) var fp FilePath @@ -1419,12 +1523,14 @@ func HandleUploadFile(cc *ClientConn, t *Transaction) (res []Transaction, err er transactionRef := cc.Server.NewTransactionRef() data := binary.BigEndian.Uint32(transactionRef) + cc.Server.mux.Lock() cc.Server.FileTransfers[data] = &FileTransfer{ FileName: fileName, FilePath: filePath, ReferenceNumber: transactionRef, Type: FileUpload, } + cc.Server.mux.Unlock() replyT := cc.NewReply(t, NewField(fieldRefNum, transactionRef))