1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
|
package hotline
import "fmt"
const (
AccessDeleteFile = 0 // File System Maintenance: Can Delete Files
AccessUploadFile = 1 // File System Maintenance: Can Upload Files
AccessDownloadFile = 2 // File System Maintenance: Can Download Files
AccessRenameFile = 3 // File System Maintenance: Can Rename Files
AccessMoveFile = 4 // File System Maintenance: Can Move Files
AccessCreateFolder = 5 // File System Maintenance: Can Create Folders
AccessDeleteFolder = 6 // File System Maintenance: Can Delete Folders
AccessRenameFolder = 7 // File System Maintenance: Can Rename Folders
AccessMoveFolder = 8 // File System Maintenance: Can Move Folders
AccessReadChat = 9 // Chat: Can Read Chat
AccessSendChat = 10 // Chat: Can Send Chat
AccessOpenChat = 11 // Chat: Can Initial Private Chat
AccessCloseChat = 12 // Present in the Hotline 1.9 protocol documentation, but seemingly unused
AccessShowInList = 13 // Present in the Hotline 1.9 protocol documentation, but seemingly unused
AccessCreateUser = 14 // User Maintenance: Can Create Accounts
AccessDeleteUser = 15 // User Maintenance: Can Delete Accounts
AccessOpenUser = 16 // User Maintenance: Can Read Accounts
AccessModifyUser = 17 // User Maintenance: Can Modify Accounts
AccessChangeOwnPass = 18 // Present in the Hotline 1.9 protocol documentation, but seemingly unused
AccessNewsReadArt = 20 // News: Can Read Articles
AccessNewsPostArt = 21 // News: Can Post Articles
AccessDisconUser = 22 // User Maintenance: Can Disconnect Users (Note: Turns username red in user list)
AccessCannotBeDiscon = 23 // User Maintenance: Cannot be Disconnected
AccessGetClientInfo = 24 // User Maintenance: Can Get User Info
AccessUploadAnywhere = 25 // File System Maintenance: Can Upload Anywhere
AccessAnyName = 26 // Miscellaneous: Can User Any Name
AccessNoAgreement = 27 // Miscellaneous: Don't Show Agreement
AccessSetFileComment = 28 // File System Maintenance: Can Comment Files
AccessSetFolderComment = 29 // File System Maintenance: Can Comment Folders
AccessViewDropBoxes = 30 // File System Maintenance: Can View Drop Boxes
AccessMakeAlias = 31 // File System Maintenance: Can Make Aliases
AccessBroadcast = 32 // Messaging: Can Broadcast
AccessNewsDeleteArt = 33 // News: Can Delete Articles
AccessNewsCreateCat = 34 // News: Can Create Categories
AccessNewsDeleteCat = 35 // News: Can Delete Categories
AccessNewsCreateFldr = 36 // News: Can Create News Bundles
AccessNewsDeleteFldr = 37 // News: Can Delete News Bundles
AccessUploadFolder = 38 // File System Maintenance: Can Upload Folders
AccessDownloadFolder = 39 // File System Maintenance: Can Download Folders
AccessSendPrivMsg = 40 // Messaging: Can Send Messages (Note: 1.9 protocol doc incorrectly says this is bit 19)
)
type AccessBitmap [8]byte
func (bits *AccessBitmap) Set(i int) {
bits[i/8] |= 1 << uint(7-i%8)
}
func (bits *AccessBitmap) IsSet(i int) bool {
return bits[i/8]&(1<<uint(7-i%8)) != 0
}
func (bits *AccessBitmap) UnmarshalYAML(unmarshal func(interface{}) error) error {
var flags interface{}
err := unmarshal(&flags)
if err != nil {
return fmt.Errorf("unmarshal access bitmap: %w", err)
}
switch v := flags.(type) {
case []interface{}:
// Mobius versions < v0.17.0 store the user access bitmap as an array of int values like:
// [96, 112, 12, 32, 3, 128, 0, 0]
// This case supports reading of user config files using this format.
for i, v := range flags.([]interface{}) {
bits[i] = byte(v.(int))
}
case map[string]interface{}:
// Mobius versions >= v0.17.0 store the user access bitmap as map[string]bool to provide a human-readable view of
// the account permissions.
accessMap := map[string]int{
"DeleteFile": AccessDeleteFile,
"UploadFile": AccessUploadFile,
"DownloadFile": AccessDownloadFile,
"RenameFile": AccessRenameFile,
"MoveFile": AccessMoveFile,
"CreateFolder": AccessCreateFolder,
"DeleteFolder": AccessDeleteFolder,
"RenameFolder": AccessRenameFolder,
"MoveFolder": AccessMoveFolder,
"ReadChat": AccessReadChat,
"SendChat": AccessSendChat,
"OpenChat": AccessOpenChat,
"CloseChat": AccessCloseChat,
"ShowInList": AccessShowInList,
"CreateUser": AccessCreateUser,
"DeleteUser": AccessDeleteUser,
"OpenUser": AccessOpenUser,
"ModifyUser": AccessModifyUser,
"ChangeOwnPass": AccessChangeOwnPass,
"NewsReadArt": AccessNewsReadArt,
"NewsPostArt": AccessNewsPostArt,
"DisconnectUser": AccessDisconUser,
"CannotBeDisconnected": AccessCannotBeDiscon,
"GetClientInfo": AccessGetClientInfo,
"UploadAnywhere": AccessUploadAnywhere,
"AnyName": AccessAnyName,
"NoAgreement": AccessNoAgreement,
"SetFileComment": AccessSetFileComment,
"SetFolderComment": AccessSetFolderComment,
"ViewDropBoxes": AccessViewDropBoxes,
"MakeAlias": AccessMakeAlias,
"Broadcast": AccessBroadcast,
"NewsDeleteArt": AccessNewsDeleteArt,
"NewsCreateCat": AccessNewsCreateCat,
"NewsDeleteCat": AccessNewsDeleteCat,
"NewsCreateFldr": AccessNewsCreateFldr,
"NewsDeleteFldr": AccessNewsDeleteFldr,
"SendPrivMsg": AccessSendPrivMsg,
"UploadFolder": AccessUploadFolder,
"DownloadFolder": AccessDownloadFolder,
}
for key, accessBit := range accessMap {
if flag, ok := v[key].(bool); ok && flag {
bits.Set(accessBit)
}
}
}
return nil
}
// accessFlags is used to render the access bitmap to human-readable boolean flags in the account yaml.
type accessFlags struct {
DownloadFile bool `yaml:"DownloadFile"`
DownloadFolder bool `yaml:"DownloadFolder"`
UploadFile bool `yaml:"UploadFile"`
UploadFolder bool `yaml:"UploadFolder"`
DeleteFile bool `yaml:"DeleteFile"`
RenameFile bool `yaml:"RenameFile"`
MoveFile bool `yaml:"MoveFile"`
CreateFolder bool `yaml:"CreateFolder"`
DeleteFolder bool `yaml:"DeleteFolder"`
RenameFolder bool `yaml:"RenameFolder"`
MoveFolder bool `yaml:"MoveFolder"`
ReadChat bool `yaml:"ReadChat"`
SendChat bool `yaml:"SendChat"`
OpenChat bool `yaml:"OpenChat"`
CloseChat bool `yaml:"CloseChat"`
ShowInList bool `yaml:"ShowInList"`
CreateUser bool `yaml:"CreateUser"`
DeleteUser bool `yaml:"DeleteUser"`
OpenUser bool `yaml:"OpenUser"`
ModifyUser bool `yaml:"ModifyUser"`
ChangeOwnPass bool `yaml:"ChangeOwnPass"`
NewsReadArt bool `yaml:"NewsReadArt"`
NewsPostArt bool `yaml:"NewsPostArt"`
DisconnectUser bool `yaml:"DisconnectUser"`
CannotBeDisconnected bool `yaml:"CannotBeDisconnected"`
GetClientInfo bool `yaml:"GetClientInfo"`
UploadAnywhere bool `yaml:"UploadAnywhere"`
AnyName bool `yaml:"AnyName"`
NoAgreement bool `yaml:"NoAgreement"`
SetFileComment bool `yaml:"SetFileComment"`
SetFolderComment bool `yaml:"SetFolderComment"`
ViewDropBoxes bool `yaml:"ViewDropBoxes"`
MakeAlias bool `yaml:"MakeAlias"`
Broadcast bool `yaml:"Broadcast"`
NewsDeleteArt bool `yaml:"NewsDeleteArt"`
NewsCreateCat bool `yaml:"NewsCreateCat"`
NewsDeleteCat bool `yaml:"NewsDeleteCat"`
NewsCreateFldr bool `yaml:"NewsCreateFldr"`
NewsDeleteFldr bool `yaml:"NewsDeleteFldr"`
SendPrivMsg bool `yaml:"SendPrivMsg"`
}
func (bits AccessBitmap) MarshalYAML() (interface{}, error) {
return accessFlags{
DownloadFile: bits.IsSet(AccessDownloadFile),
DownloadFolder: bits.IsSet(AccessDownloadFolder),
UploadFolder: bits.IsSet(AccessUploadFolder),
DeleteFile: bits.IsSet(AccessDeleteFile),
UploadFile: bits.IsSet(AccessUploadFile),
RenameFile: bits.IsSet(AccessRenameFile),
MoveFile: bits.IsSet(AccessMoveFile),
CreateFolder: bits.IsSet(AccessCreateFolder),
DeleteFolder: bits.IsSet(AccessDeleteFolder),
RenameFolder: bits.IsSet(AccessRenameFolder),
MoveFolder: bits.IsSet(AccessMoveFolder),
ReadChat: bits.IsSet(AccessReadChat),
SendChat: bits.IsSet(AccessSendChat),
OpenChat: bits.IsSet(AccessOpenChat),
CloseChat: bits.IsSet(AccessCloseChat),
ShowInList: bits.IsSet(AccessShowInList),
CreateUser: bits.IsSet(AccessCreateUser),
DeleteUser: bits.IsSet(AccessDeleteUser),
OpenUser: bits.IsSet(AccessOpenUser),
ModifyUser: bits.IsSet(AccessModifyUser),
ChangeOwnPass: bits.IsSet(AccessChangeOwnPass),
NewsReadArt: bits.IsSet(AccessNewsReadArt),
NewsPostArt: bits.IsSet(AccessNewsPostArt),
DisconnectUser: bits.IsSet(AccessDisconUser),
CannotBeDisconnected: bits.IsSet(AccessCannotBeDiscon),
GetClientInfo: bits.IsSet(AccessGetClientInfo),
UploadAnywhere: bits.IsSet(AccessUploadAnywhere),
AnyName: bits.IsSet(AccessAnyName),
NoAgreement: bits.IsSet(AccessNoAgreement),
SetFileComment: bits.IsSet(AccessSetFileComment),
SetFolderComment: bits.IsSet(AccessSetFolderComment),
ViewDropBoxes: bits.IsSet(AccessViewDropBoxes),
MakeAlias: bits.IsSet(AccessMakeAlias),
Broadcast: bits.IsSet(AccessBroadcast),
NewsDeleteArt: bits.IsSet(AccessNewsDeleteArt),
NewsCreateCat: bits.IsSet(AccessNewsCreateCat),
NewsDeleteCat: bits.IsSet(AccessNewsDeleteCat),
NewsCreateFldr: bits.IsSet(AccessNewsCreateFldr),
NewsDeleteFldr: bits.IsSet(AccessNewsDeleteFldr),
SendPrivMsg: bits.IsSet(AccessSendPrivMsg),
}, nil
}
|