]> git.r.bdr.sh - rbdr/mobius/blobdiff - hotline/transaction_handlers.go
Fix file upload stall
[rbdr/mobius] / hotline / transaction_handlers.go
index 57639674c445695cfd6c4b9f0ab83a73c78c9c99..85879a4de1bc714c0a9d18e56e9e610bf0ad5f91 100644 (file)
@@ -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) {
@@ -295,6 +299,11 @@ func HandleChatSend(cc *ClientConn, t *Transaction) (res []Transaction, err erro
 // Fields used in the reply:
 // None
 func HandleSendInstantMsg(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
+       if !cc.Authorize(accessSendPrivMsg) {
+               res = append(res, cc.NewErrReply(t, "You are not allowed to send private messages."))
+               return res, err
+       }
+
        msg := t.GetField(fieldData)
        ID := t.GetField(fieldUserID)
 
@@ -630,7 +639,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
        }
 
@@ -641,22 +650,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),
                        )
                }
        }
@@ -694,8 +703,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...))
@@ -863,9 +877,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
        }
 
@@ -873,55 +895,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
@@ -935,26 +913,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
@@ -964,15 +951,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))
 
@@ -1044,12 +1036,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
@@ -1393,15 +1421,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 {
@@ -1412,8 +1434,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
@@ -1422,14 +1442,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[:]),
@@ -1445,26 +1459,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
@@ -1478,8 +1472,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
@@ -1495,9 +1498,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 {
@@ -1513,17 +1513,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
 }
 
@@ -1542,11 +1540,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 {
@@ -1562,27 +1557,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 {
@@ -1598,6 +1588,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))
        }
 
@@ -1606,27 +1598,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 {
@@ -1636,14 +1627,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
 }
@@ -1680,7 +1673,7 @@ func HandleGetFileNameList(cc *ClientConn, t *Transaction) (res []Transaction, e
                return res, err
        }
 
-       fileNames, err := getFileNameList(fullPath)
+       fileNames, err := getFileNameList(fullPath, cc.Server.Config.IgnoreFiles)
        if err != nil {
                return res, err
        }
@@ -1728,8 +1721,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),
                ),
        )
 
@@ -1761,8 +1754,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),
                ),
        )
 
@@ -1811,8 +1804,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),
                        ),
                )
        }
@@ -1823,8 +1816,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),
                }
 
@@ -1843,7 +1836,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())
 
@@ -1925,3 +1921,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
+}