github.com/Ptt-official-app/go-bbs@v0.12.0/pttbbs/passwd.go (about) 1 // Copyright 2020 Pichu Chen, The PTT APP Authors 2 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 7 // http://www.apache.org/licenses/LICENSE-2.0 8 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package pttbbs 16 17 import ( 18 "github.com/Ptt-official-app/go-bbs/crypt" 19 20 "encoding/binary" 21 "fmt" 22 "io" 23 "log" 24 "os" 25 "strings" 26 "time" 27 ) 28 29 const ( 30 PosOfPasswdVersion = 0 31 PosOfPasswdUserID = PosOfPasswdVersion + 4 32 PosOfPasswdRealName = PosOfPasswdUserID + IDLength + 1 33 PosOfPasswdNickname = PosOfPasswdRealName + RealNameSize 34 PosOfPasswdPassword = PosOfPasswdNickname + NicknameSize 35 PosOfPasswdUserFlag = 1 + PosOfPasswdPassword + PasswordLength 36 PosOfPasswdUserLevel = 4 + PosOfPasswdUserFlag + 4 37 PosOfPasswdNumLoginDays = PosOfPasswdUserLevel + 4 38 PosOfPasswdNumPosts = PosOfPasswdNumLoginDays + 4 39 PosOfPasswdFirstLogin = PosOfPasswdNumPosts + 4 40 PosOfPasswdLastLogin = PosOfPasswdFirstLogin + 4 41 PosOfPasswdLastHost = PosOfPasswdLastLogin + 4 42 PosOfPasswdMoney = PosOfPasswdLastHost + IPV4Length + 1 43 PosOfPasswdEmail = 4 + PosOfPasswdMoney + 4 44 PosOfPasswdAddress = PosOfPasswdEmail + EmailSize 45 PosOfPasswdJustify = PosOfPasswdAddress + AddressSize 46 PosOfPasswdOver18 = 3 + PosOfPasswdJustify + RegistrationLength + 1 47 PosOfPasswdPagerUIType = PosOfPasswdOver18 + 1 48 PosOfPasswdPager = PosOfPasswdPagerUIType + 1 49 PosOfPasswdInvisible = PosOfPasswdPager + 1 50 PosOfPasswdExMailBox = 2 + PosOfPasswdInvisible + 1 51 52 PosOfPasswdCareer = 4 + PosOfPasswdExMailBox + 4 53 PosOfPasswdRole = 20 + 4 + 44 + PosOfPasswdCareer + CareerSize 54 PosOfPasswdLastSeen = PosOfPasswdRole + 4 55 PosOfPasswdTimeSetAngel = PosOfPasswdLastSeen + 4 56 PosOfPasswdTimePlayAngel = PosOfPasswdTimeSetAngel + 4 57 58 PosOfPasswdLastSong = PosOfPasswdTimePlayAngel + 4 59 PosOfPasswdLoginView = PosOfPasswdLastSong + 4 60 61 PosOfPasswdLawCounter = 1 + 1 + PosOfPasswdLoginView + 4 62 PosOfPasswdFiveWin = PosOfPasswdLawCounter + 2 63 PosOfPasswdFiveLose = PosOfPasswdFiveWin + 2 64 PosOfPasswdFiveTie = PosOfPasswdFiveLose + 2 65 PosOfPasswdChcWin = PosOfPasswdFiveTie + 2 66 PosOfPasswdChcLose = PosOfPasswdChcWin + 2 67 PosOfPasswdChcTie = PosOfPasswdChcLose + 2 68 PosOfPasswdConn6Win = PosOfPasswdChcTie + 2 69 PosOfPasswdConn6Lose = PosOfPasswdConn6Win + 2 70 PosOfPasswdConn6Tie = PosOfPasswdConn6Lose + 2 71 PosOfPasswdGoWin = 2 + PosOfPasswdConn6Tie + 2 72 PosOfPasswdGoLose = PosOfPasswdGoWin + 2 73 PosOfPasswdGoTie = PosOfPasswdGoLose + 2 74 PosOfPasswdDarkWin = PosOfPasswdGoTie + 2 75 PosOfPasswdDarkLose = PosOfPasswdDarkWin + 2 76 PosOfPasswdUaVersion = PosOfPasswdDarkLose + 2 77 78 PosOfPasswdSignature = PosOfPasswdUaVersion + 1 79 PosOfPasswdBadPost = 1 + PosOfPasswdSignature + 1 80 PosOfPasswdDarkTie = PosOfPasswdBadPost + 1 81 PosOfPasswdMyAngel = PosOfPasswdDarkTie + 2 82 83 PosOfPasswdChessEloRating = 1 + PosOfPasswdMyAngel + IDLength + 1 84 PosOfPasswdWithMe = PosOfPasswdChessEloRating + 2 85 PosOfPasswdTimeRemoveBadPost = PosOfPasswdWithMe + 4 86 PosOfPasswdTimeViolateLaw = PosOfPasswdTimeRemoveBadPost + 4 87 ) 88 89 // https://github.com/ptt/pttbbs/blob/master/include/pttstruct.h 90 91 type GameScore struct { 92 Win uint16 93 Lose uint16 94 Tie uint16 95 } 96 97 type Userec struct { 98 Version uint32 // Magic Number 99 userID string // 使用者帳號,或稱使用者 ID 100 realName string // 真實姓名 101 nickname string // 暱稱 102 password string // 密碼,預設為 crypt, 不同版本實作可能不同 103 104 userFlag uint32 // 習慣 105 UserLevel uint32 // 權限 106 numLoginDays uint32 107 numPosts uint32 108 firstLogin time.Time 109 lastLogin time.Time 110 lastHost string 111 money int32 112 113 Email string 114 Address string 115 Justify string 116 117 Over18 bool 118 PagerUIType uint8 119 Pager uint8 120 Invisible bool 121 122 ExMailBox uint32 123 124 Career string 125 Role uint32 126 LastSeen time.Time 127 TimeSetAngel time.Time 128 TimePlayAngel time.Time 129 130 LastSong time.Time 131 LoginView uint32 132 133 ViolateLaw uint16 134 Five GameScore 135 ChineseChess GameScore 136 Conn6 GameScore 137 GoChess GameScore 138 DarkChess GameScore 139 UaVersion uint8 // User Agreement Version 140 141 Signature uint8 142 BadPost uint8 143 MyAngel string 144 ChessEloRating uint16 145 WithMe uint32 146 TimeRemoveBadPost time.Time 147 TimeViolateLaw time.Time 148 } 149 150 func (u *Userec) HashedPassword() string { 151 return u.password 152 } 153 154 // VerifyPassword will check user's password is OK. it will return null 155 // when OK and error when there are something wrong 156 func (u *Userec) VerifyPassword(password string) error { 157 res, err := crypt.Fcrypt([]byte(password), []byte(u.password[:2])) 158 if err != nil { 159 return err 160 } 161 str := strings.Trim(string(res), "\x00") 162 // log.Println("res", str, err, []byte(str), []byte(u.Password)) 163 164 if str != u.password { 165 return fmt.Errorf("password incorrect") 166 } 167 return nil 168 } 169 170 func (u *Userec) UserID() string { return u.userID } 171 172 // Nickname return a string for user's nickname, this string may change 173 // depend on user's mood, return empty string if this bbs system do not support 174 func (u *Userec) Nickname() string { return u.nickname } 175 176 // RealName return a string for user's real name, this string may not be changed 177 // return empty string if this bbs system do not support 178 func (u *Userec) RealName() string { return u.realName } 179 180 // NumLoginDays return how many days this have been login since account created. 181 func (u *Userec) NumLoginDays() int { return int(u.numLoginDays) } 182 183 // NumPosts return how many posts this user has posted. 184 func (u *Userec) NumPosts() int { return int(u.numPosts) } 185 186 // Money return the money this user have. 187 func (u *Userec) Money() int { return int(u.money) } 188 189 func (u *Userec) LastLogin() time.Time { 190 return u.lastLogin 191 } 192 193 func (u *Userec) LastHost() string { 194 return u.lastHost 195 } 196 197 // UserFlag return user setting. 198 // uint32, see https://github.com/ptt/pttbbs/blob/master/include/uflags.h 199 func (u *Userec) UserFlag() uint32 { 200 return u.userFlag 201 } 202 203 func OpenUserecFile(filename string) ([]*Userec, error) { 204 file, err := os.Open(filename) 205 if err != nil { 206 log.Println(err) 207 return nil, err 208 } 209 210 ret := []*Userec{} 211 212 for { 213 buf := make([]byte, 512) 214 _, err := file.Read(buf) 215 // log.Println(len, buf, err) 216 if err == io.EOF { 217 break 218 } 219 220 f, err := UnmarshalUserec(buf) 221 if err != nil { 222 return nil, err 223 } 224 ret = append(ret, f) 225 // log.Println(f.Filename) 226 227 } 228 229 return ret, nil 230 231 } 232 233 func UnmarshalUserec(data []byte) (*Userec, error) { 234 user := &Userec{} 235 user.Version = binary.LittleEndian.Uint32(data[PosOfPasswdVersion : PosOfPasswdVersion+4]) 236 user.userID = newStringFormCString(data[PosOfPasswdUserID : PosOfPasswdUserID+IDLength+1]) 237 user.realName = newStringFormBig5UAOCString(data[PosOfPasswdRealName : PosOfPasswdRealName+RealNameSize]) 238 user.nickname = newStringFormBig5UAOCString(data[PosOfPasswdNickname : PosOfPasswdNickname+NicknameSize]) 239 user.password = newStringFormCString(data[PosOfPasswdPassword : PosOfPasswdPassword+PasswordLength]) 240 241 user.userFlag = binary.LittleEndian.Uint32(data[PosOfPasswdUserFlag : PosOfPasswdUserFlag+4]) 242 user.UserLevel = binary.LittleEndian.Uint32(data[PosOfPasswdUserLevel : PosOfPasswdUserLevel+4]) 243 user.numLoginDays = binary.LittleEndian.Uint32(data[PosOfPasswdNumLoginDays : PosOfPasswdNumLoginDays+4]) 244 user.numPosts = binary.LittleEndian.Uint32(data[PosOfPasswdNumPosts : PosOfPasswdNumPosts+4]) 245 user.firstLogin = time.Unix(int64(binary.LittleEndian.Uint32(data[PosOfPasswdFirstLogin:PosOfPasswdFirstLogin+4])), 0) 246 user.lastLogin = time.Unix(int64(binary.LittleEndian.Uint32(data[PosOfPasswdLastLogin:PosOfPasswdLastLogin+4])), 0) 247 user.lastHost = newStringFormCString(data[PosOfPasswdLastHost : PosOfPasswdLastHost+IPV4Length+1]) 248 249 user.money = int32(binary.LittleEndian.Uint32(data[PosOfPasswdMoney : PosOfPasswdMoney+4])) 250 251 user.Email = newStringFormBig5UAOCString(data[PosOfPasswdEmail : PosOfPasswdEmail+EmailSize]) 252 user.Address = newStringFormBig5UAOCString(data[PosOfPasswdAddress : PosOfPasswdAddress+AddressSize]) 253 user.Justify = newStringFormBig5UAOCString(data[PosOfPasswdJustify : PosOfPasswdJustify+RegistrationLength+1]) 254 255 user.Over18 = data[PosOfPasswdOver18] != 0 256 user.PagerUIType = data[PosOfPasswdPagerUIType] 257 user.Pager = data[PosOfPasswdPager] 258 user.Invisible = data[PosOfPasswdInvisible] != 0 259 260 user.ExMailBox = binary.LittleEndian.Uint32(data[PosOfPasswdExMailBox : PosOfPasswdExMailBox+4]) 261 262 user.Career = newStringFormBig5UAOCString(data[PosOfPasswdCareer : PosOfPasswdCareer+CareerSize]) 263 user.Role = binary.LittleEndian.Uint32(data[PosOfPasswdRole : PosOfPasswdRole+4]) 264 user.LastSeen = time.Unix(int64(binary.LittleEndian.Uint32(data[PosOfPasswdLastSeen:PosOfPasswdLastSeen+4])), 0) 265 user.TimeSetAngel = time.Unix(int64(binary.LittleEndian.Uint32(data[PosOfPasswdTimeSetAngel:PosOfPasswdTimeSetAngel+4])), 0) 266 user.TimePlayAngel = time.Unix(int64(binary.LittleEndian.Uint32(data[PosOfPasswdTimePlayAngel:PosOfPasswdTimePlayAngel+4])), 0) 267 268 user.LastSong = time.Unix(int64(binary.LittleEndian.Uint32(data[PosOfPasswdLastSong:PosOfPasswdLastSong+4])), 0) 269 user.LoginView = binary.LittleEndian.Uint32(data[PosOfPasswdLoginView : PosOfPasswdLoginView+4]) 270 user.ViolateLaw = binary.LittleEndian.Uint16(data[PosOfPasswdLawCounter : PosOfPasswdLawCounter+2]) 271 272 user.Five.Win = binary.LittleEndian.Uint16(data[PosOfPasswdFiveWin : PosOfPasswdFiveWin+2]) 273 user.Five.Lose = binary.LittleEndian.Uint16(data[PosOfPasswdFiveLose : PosOfPasswdFiveLose+2]) 274 user.Five.Tie = binary.LittleEndian.Uint16(data[PosOfPasswdFiveTie : PosOfPasswdFiveTie+2]) 275 276 user.ChineseChess.Win = binary.LittleEndian.Uint16(data[PosOfPasswdChcWin : PosOfPasswdChcWin+2]) 277 user.ChineseChess.Lose = binary.LittleEndian.Uint16(data[PosOfPasswdChcLose : PosOfPasswdChcLose+2]) 278 user.ChineseChess.Tie = binary.LittleEndian.Uint16(data[PosOfPasswdChcTie : PosOfPasswdChcTie+2]) 279 280 user.Conn6.Win = binary.LittleEndian.Uint16(data[PosOfPasswdConn6Win : PosOfPasswdConn6Win+2]) 281 user.Conn6.Lose = binary.LittleEndian.Uint16(data[PosOfPasswdConn6Lose : PosOfPasswdConn6Lose+2]) 282 user.Conn6.Tie = binary.LittleEndian.Uint16(data[PosOfPasswdConn6Tie : PosOfPasswdConn6Tie+2]) 283 284 user.GoChess.Win = binary.LittleEndian.Uint16(data[PosOfPasswdGoWin : PosOfPasswdGoWin+2]) 285 user.GoChess.Lose = binary.LittleEndian.Uint16(data[PosOfPasswdGoLose : PosOfPasswdGoLose+2]) 286 user.GoChess.Tie = binary.LittleEndian.Uint16(data[PosOfPasswdGoTie : PosOfPasswdGoTie+2]) 287 288 user.DarkChess.Win = binary.LittleEndian.Uint16(data[PosOfPasswdDarkWin : PosOfPasswdDarkWin+2]) 289 user.DarkChess.Lose = binary.LittleEndian.Uint16(data[PosOfPasswdDarkLose : PosOfPasswdDarkLose+2]) 290 user.UaVersion = data[PosOfPasswdUaVersion] 291 292 user.Signature = data[PosOfPasswdSignature] 293 user.BadPost = data[PosOfPasswdBadPost] 294 user.DarkChess.Tie = binary.LittleEndian.Uint16(data[PosOfPasswdDarkTie : PosOfPasswdDarkTie+2]) 295 user.MyAngel = newStringFormCString(data[PosOfPasswdMyAngel : PosOfPasswdMyAngel+IDLength+1+1]) 296 297 user.ChessEloRating = binary.LittleEndian.Uint16(data[PosOfPasswdChessEloRating : PosOfPasswdChessEloRating+2]) 298 user.WithMe = binary.LittleEndian.Uint32(data[PosOfPasswdWithMe : PosOfPasswdWithMe+4]) 299 user.TimeRemoveBadPost = time.Unix(int64(binary.LittleEndian.Uint32(data[PosOfPasswdTimeRemoveBadPost:PosOfPasswdTimeRemoveBadPost+4])), 0) 300 user.TimeViolateLaw = time.Unix(int64(binary.LittleEndian.Uint32(data[PosOfPasswdTimeViolateLaw:PosOfPasswdTimeViolateLaw+4])), 0) 301 302 return user, nil 303 } 304 305 func (u *Userec) MarshalBinary() ([]byte, error) { 306 ret := make([]byte, 512) 307 308 binary.LittleEndian.PutUint32(ret[PosOfPasswdVersion:PosOfPasswdVersion+4], u.Version) 309 copy(ret[PosOfPasswdUserID:PosOfPasswdUserID+IDLength+1], utf8ToBig5UAOString(u.userID)) 310 copy(ret[PosOfPasswdRealName:PosOfPasswdRealName+RealNameSize], utf8ToBig5UAOString(u.realName)) 311 copy(ret[PosOfPasswdNickname:PosOfPasswdNickname+NicknameSize], utf8ToBig5UAOString(u.nickname)) 312 copy(ret[PosOfPasswdPassword:PosOfPasswdPassword+PasswordLength], utf8ToBig5UAOString(u.password)) 313 314 binary.LittleEndian.PutUint32(ret[PosOfPasswdUserFlag:PosOfPasswdUserFlag+4], u.userFlag) 315 binary.LittleEndian.PutUint32(ret[PosOfPasswdUserLevel:PosOfPasswdUserLevel+4], u.UserLevel) 316 binary.LittleEndian.PutUint32(ret[PosOfPasswdNumLoginDays:PosOfPasswdNumLoginDays+4], u.numLoginDays) 317 binary.LittleEndian.PutUint32(ret[PosOfPasswdNumPosts:PosOfPasswdNumPosts+4], u.numPosts) 318 binary.LittleEndian.PutUint32(ret[PosOfPasswdFirstLogin:PosOfPasswdFirstLogin+4], uint32(u.firstLogin.Unix())) 319 binary.LittleEndian.PutUint32(ret[PosOfPasswdLastLogin:PosOfPasswdLastLogin+4], uint32(u.lastLogin.Unix())) 320 copy(ret[PosOfPasswdLastHost:PosOfPasswdLastHost+IPV4Length+1], utf8ToBig5UAOString(u.lastHost)) 321 binary.LittleEndian.PutUint32(ret[PosOfPasswdMoney:PosOfPasswdMoney+4], uint32(u.money)) 322 323 copy(ret[PosOfPasswdEmail:PosOfPasswdEmail+EmailSize], utf8ToBig5UAOString(u.Email)) 324 copy(ret[PosOfPasswdAddress:PosOfPasswdAddress+AddressSize], utf8ToBig5UAOString(u.Address)) 325 copy(ret[PosOfPasswdJustify:PosOfPasswdJustify+RegistrationLength], utf8ToBig5UAOString(u.Justify)) 326 327 if u.Over18 { 328 ret[PosOfPasswdOver18] = 1 329 } else { 330 ret[PosOfPasswdOver18] = 0 331 } 332 333 ret[PosOfPasswdPagerUIType] = u.PagerUIType 334 ret[PosOfPasswdPager] = u.Pager 335 336 if u.Invisible { 337 ret[PosOfPasswdInvisible] = 1 338 } else { 339 ret[PosOfPasswdInvisible] = 0 340 } 341 342 binary.LittleEndian.PutUint32(ret[PosOfPasswdExMailBox:PosOfPasswdExMailBox+4], u.ExMailBox) 343 344 copy(ret[PosOfPasswdCareer:PosOfPasswdCareer+CareerSize], utf8ToBig5UAOString(u.Career)) 345 346 binary.LittleEndian.PutUint32(ret[PosOfPasswdLastSeen:PosOfPasswdLastSeen+4], uint32(u.LastSeen.Unix())) 347 binary.LittleEndian.PutUint32(ret[PosOfPasswdTimeSetAngel:PosOfPasswdTimeSetAngel+4], uint32(u.TimeSetAngel.Unix())) 348 binary.LittleEndian.PutUint32(ret[PosOfPasswdTimePlayAngel:PosOfPasswdTimePlayAngel+4], uint32(u.TimePlayAngel.Unix())) 349 350 binary.LittleEndian.PutUint32(ret[PosOfPasswdLastSong:PosOfPasswdLastSong+4], uint32(u.LastSong.Unix())) 351 binary.LittleEndian.PutUint32(ret[PosOfPasswdLoginView:PosOfPasswdLoginView+4], u.LoginView) 352 binary.LittleEndian.PutUint16(ret[PosOfPasswdLawCounter:PosOfPasswdLawCounter+2], u.ViolateLaw) 353 354 binary.LittleEndian.PutUint16(ret[PosOfPasswdFiveWin:PosOfPasswdFiveWin+2], u.Five.Win) 355 binary.LittleEndian.PutUint16(ret[PosOfPasswdFiveLose:PosOfPasswdFiveLose+2], u.Five.Lose) 356 binary.LittleEndian.PutUint16(ret[PosOfPasswdFiveTie:PosOfPasswdFiveTie+2], u.Five.Tie) 357 358 binary.LittleEndian.PutUint16(ret[PosOfPasswdChcWin:PosOfPasswdChcWin+2], u.ChineseChess.Win) 359 binary.LittleEndian.PutUint16(ret[PosOfPasswdChcLose:PosOfPasswdChcLose+2], u.ChineseChess.Lose) 360 binary.LittleEndian.PutUint16(ret[PosOfPasswdChcTie:PosOfPasswdChcTie+2], u.ChineseChess.Tie) 361 362 binary.LittleEndian.PutUint16(ret[PosOfPasswdConn6Win:PosOfPasswdConn6Win+2], u.Conn6.Win) 363 binary.LittleEndian.PutUint16(ret[PosOfPasswdConn6Lose:PosOfPasswdConn6Lose+2], u.Conn6.Lose) 364 binary.LittleEndian.PutUint16(ret[PosOfPasswdConn6Tie:PosOfPasswdConn6Tie+2], u.Conn6.Tie) 365 366 binary.LittleEndian.PutUint16(ret[PosOfPasswdGoWin:PosOfPasswdGoWin+2], u.GoChess.Win) 367 binary.LittleEndian.PutUint16(ret[PosOfPasswdGoLose:PosOfPasswdGoLose+2], u.GoChess.Lose) 368 binary.LittleEndian.PutUint16(ret[PosOfPasswdGoTie:PosOfPasswdGoTie+2], u.GoChess.Tie) 369 370 binary.LittleEndian.PutUint16(ret[PosOfPasswdDarkWin:PosOfPasswdDarkWin+2], u.DarkChess.Win) 371 binary.LittleEndian.PutUint16(ret[PosOfPasswdDarkLose:PosOfPasswdDarkLose+2], u.DarkChess.Lose) 372 ret[PosOfPasswdUaVersion] = u.UaVersion 373 374 ret[PosOfPasswdSignature] = u.Signature 375 ret[PosOfPasswdBadPost] = u.BadPost 376 binary.LittleEndian.PutUint16(ret[PosOfPasswdDarkTie:PosOfPasswdDarkTie+2], u.DarkChess.Tie) 377 copy(ret[PosOfPasswdMyAngel:PosOfPasswdMyAngel+IDLength+1+1], utf8ToBig5UAOString(u.MyAngel)) 378 379 binary.LittleEndian.PutUint16(ret[PosOfPasswdChessEloRating:PosOfPasswdChessEloRating+2], u.ChessEloRating) 380 binary.LittleEndian.PutUint32(ret[PosOfPasswdWithMe:PosOfPasswdWithMe+4], u.WithMe) 381 binary.LittleEndian.PutUint32(ret[PosOfPasswdTimeRemoveBadPost:PosOfPasswdTimeRemoveBadPost+4], uint32(u.TimeRemoveBadPost.Unix())) 382 binary.LittleEndian.PutUint32(ret[PosOfPasswdTimeViolateLaw:PosOfPasswdTimeViolateLaw+4], uint32(u.TimeViolateLaw.Unix())) 383 384 return ret, nil 385 }