]> git.r.bdr.sh - rbdr/mobius/blobdiff - hotline/transaction_handlers.go
minor: v0.8.0
[rbdr/mobius] / hotline / transaction_handlers.go
index f080047563bda847aebba3855be3b24266d9cefc..412868c8d62242e0118cce93b797e91709ab6e8b 100644 (file)
@@ -5,12 +5,12 @@ import (
        "encoding/binary"
        "errors"
        "fmt"
-       "github.com/davecgh/go-spew/spew"
        "gopkg.in/yaml.v3"
        "io/ioutil"
        "math/big"
        "os"
        "path"
+       "path/filepath"
        "sort"
        "strings"
        "time"
@@ -87,7 +87,7 @@ var TransactionHandlers = map[uint16]TransactionType{
        },
        tranGetClientInfoText: {
                Name:    "tranGetClientInfoText",
-               Handler: HandleGetClientConnInfoText,
+               Handler: HandleGetClientInfoText,
        },
        tranGetFileInfo: {
                Name:    "tranGetFileInfo",
@@ -231,6 +231,10 @@ var TransactionHandlers = map[uint16]TransactionType{
                Name:    "tranUserBroadcast",
                Handler: HandleUserBroadcast,
        },
+       tranDownloadBanner: {
+               Name:    "tranDownloadBanner",
+               Handler: HandleDownloadBanner,
+       },
 }
 
 func HandleChatSend(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
@@ -406,7 +410,9 @@ func HandleSetFileInfo(cc *ClientConn, t *Transaction) (res []Transaction, err e
                        }
                }
 
-               hlFile.ffo.FlatFileInformationFork.setComment(t.GetField(fieldFileComment).Data)
+               if err := hlFile.ffo.FlatFileInformationFork.setComment(t.GetField(fieldFileComment).Data); err != nil {
+                       return res, err
+               }
                w, err := hlFile.infoForkWriter()
                if err != nil {
                        return res, err
@@ -521,9 +527,12 @@ func HandleMoveFile(cc *ClientConn, t *Transaction) (res []Transaction, err erro
                return res, err
        }
 
-       cc.Server.Logger.Debugw("Move file", "src", filePath+"/"+fileName, "dst", fileNewPath+"/"+fileName)
+       cc.logger.Infow("Move file", "src", filePath+"/"+fileName, "dst", fileNewPath+"/"+fileName)
 
        hlFile, err := newFileWrapper(cc.Server.FS, filePath, 0)
+       if err != nil {
+               return res, err
+       }
 
        fi, err := hlFile.dataFile()
        if err != nil {
@@ -559,11 +568,12 @@ func HandleNewFolder(cc *ClientConn, t *Transaction) (res []Transaction, err err
                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)
 
        folderName = path.Join("/", folderName)
 
+       var subPath string
+
        // fieldFilePath is only present for nested paths
        if t.GetField(fieldFilePath).Data != nil {
                var newFp FilePath
@@ -571,9 +581,12 @@ func HandleNewFolder(cc *ClientConn, t *Transaction) (res []Transaction, err err
                if err != nil {
                        return nil, err
                }
-               newFolderPath += newFp.String()
+
+               for _, pathItem := range newFp.Items {
+                       subPath = filepath.Join("/", subPath, string(pathItem.Name))
+               }
        }
-       newFolderPath = path.Join(newFolderPath, folderName)
+       newFolderPath := path.Join(cc.Server.Config.FileRoot, subPath, folderName)
 
        // TODO: check path and folder name lengths
 
@@ -621,7 +634,7 @@ func HandleSetUser(cc *ClientConn, t *Transaction) (res []Transaction, err error
        if err != nil {
                return res, err
        }
-       if err := os.WriteFile(cc.Server.ConfigDir+"Users/"+login+".yaml", out, 0666); err != nil {
+       if err := os.WriteFile(filepath.Join(cc.Server.ConfigDir, "Users", login+".yaml"), out, 0666); err != nil {
                return res, err
        }
 
@@ -632,22 +645,22 @@ func HandleSetUser(cc *ClientConn, t *Transaction) (res []Transaction, err error
                        newT := NewTransaction(tranUserAccess, c.ID, NewField(fieldUserAccess, newAccessLvl))
                        res = append(res, *newT)
 
-                       flagBitmap := big.NewInt(int64(binary.BigEndian.Uint16(*c.Flags)))
+                       flagBitmap := big.NewInt(int64(binary.BigEndian.Uint16(c.Flags)))
                        if authorize(c.Account.Access, accessDisconUser) {
                                flagBitmap.SetBit(flagBitmap, userFlagAdmin, 1)
                        } else {
                                flagBitmap.SetBit(flagBitmap, userFlagAdmin, 0)
                        }
-                       binary.BigEndian.PutUint16(*c.Flags, uint16(flagBitmap.Int64()))
+                       binary.BigEndian.PutUint16(c.Flags, uint16(flagBitmap.Int64()))
 
                        c.Account.Access = account.Access
 
                        cc.sendAll(
                                tranNotifyChangeUser,
                                NewField(fieldUserID, *c.ID),
-                               NewField(fieldUserFlags, *c.Flags),
+                               NewField(fieldUserFlags, c.Flags),
                                NewField(fieldUserName, c.UserName),
-                               NewField(fieldUserIconID, *c.Icon),
+                               NewField(fieldUserIconID, c.Icon),
                        )
                }
        }
@@ -685,8 +698,13 @@ func HandleListUsers(cc *ClientConn, t *Transaction) (res []Transaction, err err
 
        var userFields []Field
        for _, acc := range cc.Server.Accounts {
-               userField := acc.MarshalBinary()
-               userFields = append(userFields, NewField(fieldData, userField))
+               b := make([]byte, 0, 100)
+               n, err := acc.Read(b)
+               if err != nil {
+                       return res, err
+               }
+
+               userFields = append(userFields, NewField(fieldData, b[:n]))
        }
 
        res = append(res, cc.NewReply(t, userFields...))
@@ -711,7 +729,7 @@ func HandleUpdateUser(cc *ClientConn, t *Transaction) (res []Transaction, err er
 
                if len(subFields) == 1 {
                        login := DecodeUserString(getField(fieldData, &subFields).Data)
-                       cc.Server.Logger.Infow("DeleteUser", "login", login)
+                       cc.logger.Infow("DeleteUser", "login", login)
 
                        if !authorize(cc.Account.Access, accessDeleteUser) {
                                res = append(res, cc.NewErrReply(t, "You are not allowed to delete accounts."))
@@ -728,7 +746,7 @@ func HandleUpdateUser(cc *ClientConn, t *Transaction) (res []Transaction, err er
 
                // check if the login dataFile; 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)
+                       cc.logger.Infow("UpdateUser", "login", login)
 
                        // account dataFile, so this is an update action
                        if !authorize(cc.Account.Access, accessModifyUser) {
@@ -758,7 +776,7 @@ func HandleUpdateUser(cc *ClientConn, t *Transaction) (res []Transaction, err er
                                return res, err
                        }
                } else {
-                       cc.Server.Logger.Infow("CreateUser", "login", login)
+                       cc.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."))
@@ -854,9 +872,17 @@ func byteToInt(bytes []byte) (int, error) {
        return 0, errors.New("unknown byte length")
 }
 
-func HandleGetClientConnInfoText(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
+// HandleGetClientInfoText returns user information for the specific user.
+//
+// Fields used in the request:
+// 103 User ID
+//
+// Fields used in the reply:
+// 102 User name
+// 101 Data            User info text string
+func HandleGetClientInfoText(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"))
+               res = append(res, cc.NewErrReply(t, "You are not allowed to get client info."))
                return res, err
        }
 
@@ -864,55 +890,11 @@ func HandleGetClientConnInfoText(cc *ClientConn, t *Transaction) (res []Transact
 
        clientConn := cc.Server.Clients[uint16(clientID)]
        if clientConn == nil {
-               return res, errors.New("invalid client")
-       }
-
-       // TODO: Implement non-hardcoded values
-       template := `Nickname:   %s
-Name:       %s
-Account:    %s
-Address:    %s
-
--------- File Downloads ---------
-
-%s
-
-------- Folder Downloads --------
-
-None.
-
---------- File Uploads ----------
-
-None.
-
--------- Folder Uploads ---------
-
-None.
-
-------- Waiting Downloads -------
-
-None.
-
-       `
-
-       activeDownloads := clientConn.Transfers[FileDownload]
-       activeDownloadList := "None."
-       for _, dl := range activeDownloads {
-               activeDownloadList += dl.String() + "\n"
+               return append(res, cc.NewErrReply(t, "User not found.")), err
        }
 
-       template = fmt.Sprintf(
-               template,
-               clientConn.UserName,
-               clientConn.Account.Name,
-               clientConn.Account.Login,
-               clientConn.RemoteAddr,
-               activeDownloadList,
-       )
-       template = strings.Replace(template, "\n", "\r", -1)
-
        res = append(res, cc.NewReply(t,
-               NewField(fieldData, []byte(template)),
+               NewField(fieldData, []byte(clientConn.String())),
                NewField(fieldUserName, clientConn.UserName),
        ))
        return res, err
@@ -926,24 +908,35 @@ func HandleGetUserNameList(cc *ClientConn, t *Transaction) (res []Transaction, e
 
 func HandleTranAgreed(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
        cc.Agreed = true
-       cc.UserName = t.GetField(fieldUserName).Data
-       *cc.Icon = t.GetField(fieldUserIconID).Data
+
+       if t.GetField(fieldUserName).Data != nil {
+               if cc.Authorize(accessAnyName) {
+                       cc.UserName = t.GetField(fieldUserName).Data
+               } else {
+                       cc.UserName = []byte(cc.Account.Name)
+               }
+       }
+
+       cc.Icon = t.GetField(fieldUserIconID).Data
+
+       cc.logger = cc.logger.With("name", string(cc.UserName))
+       cc.logger.Infow("Login successful", "clientVersion", fmt.Sprintf("%x", cc.Version))
 
        options := t.GetField(fieldOptions).Data
        optBitmap := big.NewInt(int64(binary.BigEndian.Uint16(options)))
 
-       flagBitmap := big.NewInt(int64(binary.BigEndian.Uint16(*cc.Flags)))
+       flagBitmap := big.NewInt(int64(binary.BigEndian.Uint16(cc.Flags)))
 
        // Check refuse private PM option
        if optBitmap.Bit(refusePM) == 1 {
                flagBitmap.SetBit(flagBitmap, userFlagRefusePM, 1)
-               binary.BigEndian.PutUint16(*cc.Flags, uint16(flagBitmap.Int64()))
+               binary.BigEndian.PutUint16(cc.Flags, uint16(flagBitmap.Int64()))
        }
 
        // Check refuse private chat option
        if optBitmap.Bit(refuseChat) == 1 {
                flagBitmap.SetBit(flagBitmap, userFLagRefusePChat, 1)
-               binary.BigEndian.PutUint16(*cc.Flags, uint16(flagBitmap.Int64()))
+               binary.BigEndian.PutUint16(cc.Flags, uint16(flagBitmap.Int64()))
        }
 
        // Check auto response
@@ -953,15 +946,20 @@ func HandleTranAgreed(cc *ClientConn, t *Transaction) (res []Transaction, err er
                cc.AutoReply = []byte{}
        }
 
-       cc.notifyOthers(
+       trans := cc.notifyOthers(
                *NewTransaction(
                        tranNotifyChangeUser, nil,
                        NewField(fieldUserName, cc.UserName),
                        NewField(fieldUserID, *cc.ID),
-                       NewField(fieldUserIconID, *cc.Icon),
-                       NewField(fieldUserFlags, *cc.Flags),
+                       NewField(fieldUserIconID, cc.Icon),
+                       NewField(fieldUserFlags, cc.Flags),
                ),
        )
+       res = append(res, trans...)
+
+       if cc.Server.Config.BannerFile != "" {
+               res = append(res, *NewTransaction(tranServerBanner, cc.ID, NewField(fieldBannerType, []byte("JPEG"))))
+       }
 
        res = append(res, cc.NewReply(t))
 
@@ -1033,12 +1031,48 @@ func HandleDisconnectUser(cc *ClientConn, t *Transaction) (res []Transaction, er
                return res, err
        }
 
-       if err := clientConn.Connection.Close(); err != nil {
-               return res, err
+       // If fieldOptions is set, then the client IP is banned in addition to disconnected.
+       // 00 01 = temporary ban
+       // 00 02 = permanent ban
+       if t.GetField(fieldOptions).Data != nil {
+               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))
+
+                       res = append(res, *NewTransaction(
+                               tranServerMsg,
+                               clientConn.ID,
+                               NewField(fieldData, []byte("You are temporarily banned on this server")),
+                               NewField(fieldChatOptions, []byte{0, 0}),
+                       ))
+
+                       banUntil := time.Now().Add(tempBanDuration)
+                       cc.Server.banList[strings.Split(clientConn.RemoteAddr, ":")[0]] = &banUntil
+                       cc.Server.writeBanList()
+               case 2:
+                       // send message: "You are permanently banned on this server"
+                       cc.logger.Infow("Disconnect & ban " + string(clientConn.UserName))
+
+                       res = append(res, *NewTransaction(
+                               tranServerMsg,
+                               clientConn.ID,
+                               NewField(fieldData, []byte("You are permanently banned on this server")),
+                               NewField(fieldChatOptions, []byte{0, 0}),
+                       ))
+
+                       cc.Server.banList[strings.Split(clientConn.RemoteAddr, ":")[0]] = nil
+                       cc.Server.writeBanList()
+               }
        }
 
-       res = append(res, cc.NewReply(t))
-       return res, err
+       // TODO: remove this awful hack
+       go func() {
+               time.Sleep(1 * time.Second)
+               clientConn.Disconnect()
+       }()
+
+       return append(res, cc.NewReply(t)), err
 }
 
 // HandleGetNewsCatNameList returns a list of news categories for a path
@@ -1050,9 +1084,6 @@ func HandleGetNewsCatNameList(cc *ClientConn, t *Transaction) (res []Transaction
                return res, err
        }
 
-       newsPath := t.GetField(fieldNewsPath).Data
-       cc.Server.Logger.Infow("NewsPath: ", "np", string(newsPath))
-
        pathStrs := ReadNewsPath(t.GetField(fieldNewsPath).Data)
        cats := cc.Server.GetNewsCatByPath(pathStrs)
 
@@ -1115,7 +1146,7 @@ func HandleNewNewsFldr(cc *ClientConn, t *Transaction) (res []Transaction, err e
        name := string(t.GetField(fieldFileName).Data)
        pathStrs := ReadNewsPath(t.GetField(fieldNewsPath).Data)
 
-       cc.Server.Logger.Infof("Creating new news folder %s", name)
+       cc.logger.Infof("Creating new news folder %s", name)
 
        cats := cc.Server.GetNewsCatByPath(pathStrs)
        cats[name] = NewsCategoryListData15{
@@ -1220,7 +1251,7 @@ func HandleDelNewsItem(cc *ClientConn, t *Transaction) (res []Transaction, err e
 
        // TODO: determine if path is a Folder (Bundle) or Category and check for permission
 
-       cc.Server.Logger.Infof("DelNewsItem %v", pathStrs)
+       cc.logger.Infof("DelNewsItem %v", pathStrs)
 
        cats := cc.Server.ThreadedNews.Categories
 
@@ -1385,15 +1416,9 @@ func HandleDownloadFile(cc *ClientConn, t *Transaction) (res []Transaction, err
                return res, err
        }
 
-       transactionRef := cc.Server.NewTransactionRef()
-       data := binary.BigEndian.Uint32(transactionRef)
+       xferSize := hlFile.ffo.TransferSize(0)
 
-       ft := &FileTransfer{
-               FileName:        fileName,
-               FilePath:        filePath,
-               ReferenceNumber: transactionRef,
-               Type:            FileDownload,
-       }
+       ft := cc.newFileTransfer(FileDownload, fileName, filePath, xferSize)
 
        // TODO: refactor to remove this
        if resumeData != nil {
@@ -1404,8 +1429,6 @@ func HandleDownloadFile(cc *ClientConn, t *Transaction) (res []Transaction, err
                ft.fileResumeData = &frd
        }
 
-       xferSize := hlFile.ffo.TransferSize(0)
-
        // 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
@@ -1414,14 +1437,8 @@ func HandleDownloadFile(cc *ClientConn, t *Transaction) (res []Transaction, err
                xferSize = hlFile.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(fieldRefNum, ft.refNum[:]),
                NewField(fieldWaitingCount, []byte{0x00, 0x00}), // TODO: Implement waiting count
                NewField(fieldTransferSize, xferSize),
                NewField(fieldFileSize, hlFile.ffo.FlatFileDataForkHeader.DataSize[:]),
@@ -1437,26 +1454,6 @@ func HandleDownloadFolder(cc *ClientConn, t *Transaction) (res []Transaction, er
                return res, err
        }
 
-       transactionRef := cc.Server.NewTransactionRef()
-       data := binary.BigEndian.Uint32(transactionRef)
-
-       fileTransfer := &FileTransfer{
-               FileName:        t.GetField(fieldFileName).Data,
-               FilePath:        t.GetField(fieldFilePath).Data,
-               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
-       err = fp.UnmarshalBinary(t.GetField(fieldFilePath).Data)
-       if err != nil {
-               return res, err
-       }
-
        fullFilePath, err := readPath(cc.Server.Config.FileRoot, t.GetField(fieldFilePath).Data, t.GetField(fieldFileName).Data)
        if err != nil {
                return res, err
@@ -1470,8 +1467,17 @@ func HandleDownloadFolder(cc *ClientConn, t *Transaction) (res []Transaction, er
        if err != nil {
                return res, err
        }
+
+       fileTransfer := cc.newFileTransfer(FolderDownload, t.GetField(fieldFileName).Data, t.GetField(fieldFilePath).Data, transferSize)
+
+       var fp FilePath
+       err = fp.UnmarshalBinary(t.GetField(fieldFilePath).Data)
+       if err != nil {
+               return res, err
+       }
+
        res = append(res, cc.NewReply(t,
-               NewField(fieldRefNum, transactionRef),
+               NewField(fieldRefNum, fileTransfer.ReferenceNumber),
                NewField(fieldTransferSize, transferSize),
                NewField(fieldFolderItemCount, itemCount),
                NewField(fieldWaitingCount, []byte{0x00, 0x00}), // TODO: Implement waiting count
@@ -1487,9 +1493,6 @@ func HandleDownloadFolder(cc *ClientConn, t *Transaction) (res []Transaction, er
 // 220 Folder item count
 // 204 File transfer options   "Optional Currently set to 1" (TODO: ??)
 func HandleUploadFolder(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
-       transactionRef := cc.Server.NewTransactionRef()
-       data := binary.BigEndian.Uint32(transactionRef)
-
        var fp FilePath
        if t.GetField(fieldFilePath).Data != nil {
                if err = fp.UnmarshalBinary(t.GetField(fieldFilePath).Data); err != nil {
@@ -1505,17 +1508,15 @@ func HandleUploadFolder(cc *ClientConn, t *Transaction) (res []Transaction, err
                }
        }
 
-       fileTransfer := &FileTransfer{
-               FileName:        t.GetField(fieldFileName).Data,
-               FilePath:        t.GetField(fieldFilePath).Data,
-               ReferenceNumber: transactionRef,
-               Type:            FolderUpload,
-               FolderItemCount: t.GetField(fieldFolderItemCount).Data,
-               TransferSize:    t.GetField(fieldTransferSize).Data,
-       }
-       cc.Server.FileTransfers[data] = fileTransfer
+       fileTransfer := cc.newFileTransfer(FolderUpload,
+               t.GetField(fieldFileName).Data,
+               t.GetField(fieldFilePath).Data,
+               t.GetField(fieldTransferSize).Data,
+       )
+
+       fileTransfer.FolderItemCount = t.GetField(fieldFolderItemCount).Data
 
-       res = append(res, cc.NewReply(t, NewField(fieldRefNum, transactionRef)))
+       res = append(res, cc.NewReply(t, NewField(fieldRefNum, fileTransfer.ReferenceNumber)))
        return res, err
 }
 
@@ -1534,11 +1535,8 @@ func HandleUploadFile(cc *ClientConn, t *Transaction) (res []Transaction, err er
 
        fileName := t.GetField(fieldFileName).Data
        filePath := t.GetField(fieldFilePath).Data
-
        transferOptions := t.GetField(fieldFileTransferOptions).Data
-
-       // TODO: is this field useful for anything?
-       // transferSize := t.GetField(fieldTransferSize).Data
+       transferSize := t.GetField(fieldTransferSize).Data // not sent for resume
 
        var fp FilePath
        if filePath != nil {
@@ -1554,27 +1552,22 @@ func HandleUploadFile(cc *ClientConn, t *Transaction) (res []Transaction, err er
                        return res, err
                }
        }
+       fullFilePath, err := readPath(cc.Server.Config.FileRoot, filePath, fileName)
+       if err != nil {
+               return res, err
+       }
 
-       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,
+       if _, err := cc.Server.FS.Stat(fullFilePath); err == nil {
+               res = append(res, cc.NewErrReply(t, fmt.Sprintf("Cannot accept upload because there is already a file named \"%v\".  Try choosing a different name.", string(fileName))))
+               return res, err
        }
-       cc.Server.mux.Unlock()
 
-       replyT := cc.NewReply(t, NewField(fieldRefNum, transactionRef))
+       ft := cc.newFileTransfer(FileUpload, fileName, filePath, transferSize)
+
+       replyT := cc.NewReply(t, NewField(fieldRefNum, ft.ReferenceNumber))
 
        // client has requested to resume a partially transferred file
        if transferOptions != nil {
-               fullFilePath, err := readPath(cc.Server.Config.FileRoot, filePath, fileName)
-               if err != nil {
-                       return res, err
-               }
 
                fileInfo, err := cc.Server.FS.Stat(fullFilePath + incompleteFileSuffix)
                if err != nil {
@@ -1590,6 +1583,8 @@ func HandleUploadFile(cc *ClientConn, t *Transaction) (res []Transaction, err er
 
                b, _ := fileResumeData.BinaryMarshal()
 
+               ft.TransferSize = offset
+
                replyT.Fields = append(replyT.Fields, NewField(fieldFileResumeData, b))
        }
 
@@ -1598,27 +1593,26 @@ func HandleUploadFile(cc *ClientConn, t *Transaction) (res []Transaction, err er
 }
 
 func HandleSetClientUserInfo(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
-       var icon []byte
        if len(t.GetField(fieldUserIconID).Data) == 4 {
-               icon = t.GetField(fieldUserIconID).Data[2:]
+               cc.Icon = t.GetField(fieldUserIconID).Data[2:]
        } else {
-               icon = t.GetField(fieldUserIconID).Data
+               cc.Icon = t.GetField(fieldUserIconID).Data
+       }
+       if cc.Authorize(accessAnyName) {
+               cc.UserName = t.GetField(fieldUserName).Data
        }
-       *cc.Icon = icon
-       cc.UserName = t.GetField(fieldUserName).Data
 
        // the options field is only passed by the client versions > 1.2.3.
        options := t.GetField(fieldOptions).Data
-
        if options != nil {
                optBitmap := big.NewInt(int64(binary.BigEndian.Uint16(options)))
-               flagBitmap := big.NewInt(int64(binary.BigEndian.Uint16(*cc.Flags)))
+               flagBitmap := big.NewInt(int64(binary.BigEndian.Uint16(cc.Flags)))
 
                flagBitmap.SetBit(flagBitmap, userFlagRefusePM, optBitmap.Bit(refusePM))
-               binary.BigEndian.PutUint16(*cc.Flags, uint16(flagBitmap.Int64()))
+               binary.BigEndian.PutUint16(cc.Flags, uint16(flagBitmap.Int64()))
 
                flagBitmap.SetBit(flagBitmap, userFLagRefusePChat, optBitmap.Bit(refuseChat))
-               binary.BigEndian.PutUint16(*cc.Flags, uint16(flagBitmap.Int64()))
+               binary.BigEndian.PutUint16(cc.Flags, uint16(flagBitmap.Int64()))
 
                // Check auto response
                if optBitmap.Bit(autoResponse) == 1 {
@@ -1628,14 +1622,16 @@ func HandleSetClientUserInfo(cc *ClientConn, t *Transaction) (res []Transaction,
                }
        }
 
-       // Notify all clients of updated user info
-       cc.sendAll(
-               tranNotifyChangeUser,
-               NewField(fieldUserID, *cc.ID),
-               NewField(fieldUserIconID, *cc.Icon),
-               NewField(fieldUserFlags, *cc.Flags),
-               NewField(fieldUserName, cc.UserName),
-       )
+       for _, c := range sortedClients(cc.Server.Clients) {
+               res = append(res, *NewTransaction(
+                       tranNotifyChangeUser,
+                       c.ID,
+                       NewField(fieldUserID, *cc.ID),
+                       NewField(fieldUserIconID, cc.Icon),
+                       NewField(fieldUserFlags, cc.Flags),
+                       NewField(fieldUserName, cc.UserName),
+               ))
+       }
 
        return res, err
 }
@@ -1659,8 +1655,6 @@ func HandleGetFileNameList(cc *ClientConn, t *Transaction) (res []Transaction, e
                return res, err
        }
 
-       spew.Dump(fullPath)
-
        var fp FilePath
        if t.GetField(fieldFilePath).Data != nil {
                if err = fp.UnmarshalBinary(t.GetField(fieldFilePath).Data); err != nil {
@@ -1670,11 +1664,11 @@ func HandleGetFileNameList(cc *ClientConn, t *Transaction) (res []Transaction, e
 
        // Handle special case for drop box folders
        if fp.IsDropbox() && !authorize(cc.Account.Access, accessViewDropBoxes) {
-               res = append(res, cc.NewReply(t))
+               res = append(res, cc.NewErrReply(t, "You are not allowed to view drop boxes."))
                return res, err
        }
 
-       fileNames, err := getFileNameList(fullPath)
+       fileNames, err := getFileNameList(fullPath, cc.Server.Config.IgnoreFiles)
        if err != nil {
                return res, err
        }
@@ -1722,8 +1716,8 @@ func HandleInviteNewChat(cc *ClientConn, t *Transaction) (res []Transaction, err
                        NewField(fieldChatID, newChatID),
                        NewField(fieldUserName, cc.UserName),
                        NewField(fieldUserID, *cc.ID),
-                       NewField(fieldUserIconID, *cc.Icon),
-                       NewField(fieldUserFlags, *cc.Flags),
+                       NewField(fieldUserIconID, cc.Icon),
+                       NewField(fieldUserFlags, cc.Flags),
                ),
        )
 
@@ -1755,8 +1749,8 @@ func HandleInviteToChat(cc *ClientConn, t *Transaction) (res []Transaction, err
                        NewField(fieldChatID, chatID),
                        NewField(fieldUserName, cc.UserName),
                        NewField(fieldUserID, *cc.ID),
-                       NewField(fieldUserIconID, *cc.Icon),
-                       NewField(fieldUserFlags, *cc.Flags),
+                       NewField(fieldUserIconID, cc.Icon),
+                       NewField(fieldUserFlags, cc.Flags),
                ),
        )
 
@@ -1805,8 +1799,8 @@ func HandleJoinChat(cc *ClientConn, t *Transaction) (res []Transaction, err erro
                                NewField(fieldChatID, chatID),
                                NewField(fieldUserName, cc.UserName),
                                NewField(fieldUserID, *cc.ID),
-                               NewField(fieldUserIconID, *cc.Icon),
-                               NewField(fieldUserFlags, *cc.Flags),
+                               NewField(fieldUserIconID, cc.Icon),
+                               NewField(fieldUserFlags, cc.Flags),
                        ),
                )
        }
@@ -1817,8 +1811,8 @@ func HandleJoinChat(cc *ClientConn, t *Transaction) (res []Transaction, err erro
        for _, c := range sortedClients(privChat.ClientConn) {
                user := User{
                        ID:    *c.ID,
-                       Icon:  *c.Icon,
-                       Flags: *c.Flags,
+                       Icon:  c.Icon,
+                       Flags: c.Flags,
                        Name:  string(c.UserName),
                }
 
@@ -1837,7 +1831,10 @@ func HandleLeaveChat(cc *ClientConn, t *Transaction) (res []Transaction, err err
        chatID := t.GetField(fieldChatID).Data
        chatInt := binary.BigEndian.Uint32(chatID)
 
-       privChat := cc.Server.PrivateChats[chatInt]
+       privChat, ok := cc.Server.PrivateChats[chatInt]
+       if !ok {
+               return res, nil
+       }
 
        delete(privChat.ClientConn, cc.uint16ID())
 
@@ -1909,7 +1906,7 @@ func HandleMakeAlias(cc *ClientConn, t *Transaction) (res []Transaction, err err
                return res, err
        }
 
-       cc.Server.Logger.Debugw("Make alias", "src", fullFilePath, "dst", fullNewFilePath)
+       cc.logger.Debugw("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"))
@@ -1919,3 +1916,21 @@ func HandleMakeAlias(cc *ClientConn, t *Transaction) (res []Transaction, err err
        res = append(res, cc.NewReply(t))
        return res, err
 }
+
+func HandleDownloadBanner(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
+       fi, err := cc.Server.FS.Stat(filepath.Join(cc.Server.ConfigDir, cc.Server.Config.BannerFile))
+       if err != nil {
+               return res, err
+       }
+
+       ft := cc.newFileTransfer(bannerDownload, []byte{}, []byte{}, make([]byte, 4))
+
+       binary.BigEndian.PutUint32(ft.TransferSize, uint32(fi.Size()))
+
+       res = append(res, cc.NewReply(t,
+               NewField(fieldRefNum, ft.refNum[:]),
+               NewField(fieldTransferSize, ft.TransferSize),
+       ))
+
+       return res, err
+}