db.go (27342B)
1 // Database functions. 2 // Database is the file with line-bundles: msgid:base64 encoded msg. 3 // File db.idx is created and mantained automatically. 4 // There is also points.txt, db of users. 5 package ii 6 7 import ( 8 "bufio" 9 "crypto/sha256" 10 "errors" 11 "fmt" 12 "io" 13 "os" 14 "path/filepath" 15 "regexp" 16 "sort" 17 "strings" 18 "sync" 19 "sync/atomic" 20 "time" 21 "golang.org/x/crypto/bcrypt" 22 ) 23 24 // This is index entry. Information about message that is loaded in memory. 25 // So, the index could not be very huge. 26 // Num: sequence number. 27 // Id: MsgId 28 // Echo: Echoarea 29 // To, From, Repto: message attributes 30 // Off: offset to bundle-line in database (in bytes) 31 type MsgInfo struct { 32 Num int 33 Id string 34 Echo string 35 To string 36 Off int64 37 Repto string 38 From string 39 Topic string 40 } 41 42 // Index object. Holds List and Hash for all MsgInfo entries 43 // FileSize is used to auto reread new entries if it has changed by 44 // someone. 45 type Index struct { 46 Hash map[string]*MsgInfo 47 List []string 48 FileSize int64 49 } 50 51 // Database object. Returns by OpenDB. 52 // Idx: Index structure (like dictionary). 53 // Name: database name, 'db' by default. 54 // Sync: used to syncronize access to DB from goroutines (many readers, one writer). 55 // IdxSync: same, but for Index. 56 // LockDepth: used for recursive file lock, to avoid conflict between idecctl and idecd. 57 type DB struct { 58 Path string 59 Idx Index 60 Sync sync.RWMutex 61 IdxSync sync.RWMutex 62 Name string 63 LockDepth int32 64 } 65 66 // Utility function. Just append line (text) to file (fn) 67 func append_file(fn string, text string) error { 68 f, err := os.OpenFile(fn, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 69 if err != nil { 70 return err 71 } 72 defer f.Close() 73 if _, err := f.WriteString(text + "\n"); err != nil { 74 return err 75 } 76 return nil 77 } 78 79 // Recursive file lock. Used to avoid conflicts between idecctl and idecd. 80 // Uses mkdir as atomic operation. 81 // Note: dirs created as db.LockPath() 82 // 16 sec is limit. 83 func (db *DB) Lock() bool { 84 if atomic.AddInt32(&db.LockDepth, 1) > 1 { 85 return true 86 } 87 try := 16 88 for try > 0 { 89 if err := os.Mkdir(db.LockPath(), 0777); err == nil { 90 return true 91 } 92 time.Sleep(time.Second) 93 try -= 1 94 } 95 Error.Printf("Can not acquire lock for 16 seconds: %s", db.LockPath()) 96 return false 97 } 98 99 // Recursive file lock: unlock 100 // See Lock comment. 101 func (db *DB) Unlock() { 102 if atomic.AddInt32(&db.LockDepth, -1) > 0 { 103 return 104 } 105 os.Remove(db.LockPath()) 106 } 107 108 // Returns path to index file. 109 func (db *DB) IndexPath() string { 110 return fmt.Sprintf("%s.idx", db.Path) 111 } 112 113 // Return path to database itself 114 func (db *DB) BundlePath() string { 115 return db.Path 116 } 117 118 // Returns path to lock. 119 func (db *DB) LockPath() string { 120 pat := strings.Replace(db.Path, "/", "_", -1) 121 return fmt.Sprintf("%s/%s-bundle.lock", os.TempDir(), pat) 122 } 123 124 // var MaxMsgLen int = 128 * 1024 * 1024 125 126 // This function creates index. It locks. 127 func (db *DB) CreateIndex() error { 128 db.Sync.Lock() 129 defer db.Sync.Unlock() 130 db.Lock() 131 defer db.Unlock() 132 133 return db._CreateIndex() 134 } 135 136 // Utility to pass all lines of file (path) to fn(line). 137 // Stops on EOF or fn returns false. 138 func FileLines(path string, fn func(string) bool) error { 139 f, err := os.Open(path) 140 if err != nil { 141 if os.IsNotExist(err) { 142 return nil 143 } 144 return err 145 } 146 defer f.Close() 147 return f_lines(f, fn) 148 } 149 150 // Internal function to implement FileLines. Works with 151 // file by *File object. 152 func f_lines(f *os.File, fn func(string) bool) error { 153 reader := bufio.NewReader(f) 154 for { 155 line, err := reader.ReadString('\n') 156 if err != nil && err != io.EOF { 157 return err 158 } 159 line = strings.TrimSuffix(line, "\n") 160 if err == io.EOF { 161 break 162 } 163 if !fn(line) { 164 break 165 } 166 } 167 // scanner := bufio.NewScanner(f) 168 // scanner.Buffer(make([]byte, MaxMsgLen), MaxMsgLen) 169 170 // for scanner.Scan() { 171 // line := scanner.Text() 172 // if !fn(line) { 173 // break 174 // } 175 // } 176 return nil 177 } 178 179 // Internal function of CreateIndex. 180 // Does not lock! 181 func (db *DB) _CreateIndex() error { 182 fidx, err := os.OpenFile(db.IndexPath(), os.O_CREATE|os.O_WRONLY, 0644) 183 if err != nil { 184 return err 185 } 186 defer fidx.Close() 187 var off int64 188 return FileLines(db.BundlePath(), func(line string) bool { 189 msg, _ := DecodeBundle(line) 190 if msg == nil { 191 off += int64(len(line) + 1) 192 return true 193 } 194 repto, _ := msg.Tag("repto") 195 ioff := off 196 if v, _ := msg.Tag("access"); v == "blacklist" { 197 ioff = -1 198 } 199 200 _, err := fidx.WriteString(fmt.Sprintf("%s:%s:%d:%s:%s:%s\n", 201 msg.MsgId, msg.Echo, ioff, msg.To, msg.From, repto)) 202 if err != nil { 203 // FIXME: handle this error 204 panic(err) 205 } 206 off += int64(len(line) + 1) 207 return true 208 }) 209 } 210 211 // Internal function. Create and open new index. 212 func (db *DB) _ReopenIndex() (*os.File, error) { 213 err := db._CreateIndex() 214 if err != nil { 215 return nil, err 216 } 217 file, err := os.Open(db.IndexPath()) 218 if err != nil { 219 return nil, err 220 } 221 return file, nil 222 } 223 224 // Loads index. If index doesent exists, create and load it. 225 // If index was changed, reread tail. 226 // This function does lock. 227 func (db *DB) LoadIndex() error { 228 db.IdxSync.Lock() 229 defer db.IdxSync.Unlock() 230 var Idx Index 231 file, err := os.Open(db.IndexPath()) 232 if err != nil { 233 db.Idx = Idx 234 if os.IsNotExist(err) { 235 file, err = db._ReopenIndex() 236 if err != nil { 237 Error.Printf("Can not seek to end of index") 238 return err 239 } 240 } else { 241 Error.Printf("Can not open index: %s", err) 242 return err 243 } 244 } 245 defer file.Close() 246 247 info, err := file.Stat() 248 if err != nil { 249 Error.Printf("Can not stat index: %s", err) 250 return err 251 } 252 fsize := info.Size() 253 254 if db.Idx.Hash != nil { // already loaded 255 if fsize > db.Idx.FileSize { 256 Trace.Printf("Refreshing index file...%d>%d", fsize, db.Idx.FileSize) 257 if _, err := file.Seek(db.Idx.FileSize, 0); err != nil { 258 Error.Printf("Can not seek index: %s", err) 259 return err 260 } 261 Idx = db.Idx 262 // rebuild topics 263 for _, v := range Idx.Hash { 264 v.Topic = "" 265 } 266 } else if info.Size() < db.Idx.FileSize { 267 Info.Printf("Index file truncated, rebuild inndex...") 268 file, err = db._ReopenIndex() 269 if err != nil { 270 Error.Printf("Can not reopen index: %s", err) 271 return err 272 } 273 defer file.Close() 274 } else { 275 return nil 276 } 277 } else { 278 Idx.Hash = make(map[string]*MsgInfo) 279 } 280 var err2 error 281 linenr := 0 282 nr := len(Idx.List) 283 err = f_lines(file, func(line string) bool { 284 linenr++ 285 info := strings.Split(line, ":") 286 if len(info) < 6 { 287 err2 = errors.New("Wrong format on line:" + fmt.Sprintf("%d", linenr)) 288 return false 289 } 290 mi := MsgInfo{Num: nr, Id: info[0], Echo: info[1], To: info[3], From: info[4]} 291 if _, err := fmt.Sscanf(info[2], "%d", &mi.Off); err != nil { 292 err2 = errors.New("Wrong offset on line: " + fmt.Sprintf("%d", linenr)) 293 return false 294 } 295 mi.Repto = info[5] 296 if mm, ok := Idx.Hash[mi.Id]; !ok { // new msg 297 Idx.List = append(Idx.List, mi.Id) 298 nr++ 299 } else { 300 mi.Num = mm.Num 301 } 302 Idx.Hash[mi.Id] = &mi 303 // Trace.Printf("Adding %s to index", mi.Id) 304 return true 305 }) 306 if err != nil { 307 Error.Printf("Can not parse index: %s", err) 308 return err 309 } 310 if err2 != nil { 311 Error.Printf("Can not parse index: %s", err2) 312 return err2 313 } 314 Idx.FileSize = fsize 315 db.Idx = Idx 316 return nil 317 } 318 319 // Internal function to Lookup message in loaded index. 320 // If idx parameter is true, load and created index. 321 // Returns MsgInfo pointer or nil if fails. 322 // Does lock! 323 // bl: look in blacklisted messages too? 324 func (db *DB) _Lookup(Id string, bl bool, idx bool) *MsgInfo { 325 if idx { 326 if err := db.LoadIndex(); err != nil { 327 return nil 328 } 329 } 330 db.IdxSync.RLock() 331 defer db.IdxSync.RUnlock() 332 info, ok := db.Idx.Hash[Id] 333 if !ok || (!bl && info.Off < 0) { 334 return nil 335 } 336 return info 337 } 338 339 // Lookup variant, but without locking. 340 // Useful if caller do locking logic himself. 341 func (db *DB) LookupFast(Id string, bl bool) *MsgInfo { 342 if Id == "" { 343 return nil 344 } 345 return db._Lookup(Id, bl, false) 346 } 347 348 // Lookup message in index. 349 // Do not search blacklisted messages. 350 // Creates/load index if needed. 351 // Returns MsgInfo pointer. 352 // Does lock! 353 func (db *DB) Lookup(Id string) *MsgInfo { 354 db.Sync.RLock() 355 defer db.Sync.RUnlock() 356 db.Lock() 357 defer db.Unlock() 358 359 return db._Lookup(Id, false, true) 360 } 361 362 // Same as Lookup, but checks in blacklisted messages too 363 func (db *DB) Exists(Id string) *MsgInfo { 364 db.Sync.RLock() 365 defer db.Sync.RUnlock() 366 db.Lock() 367 defer db.Unlock() 368 369 return db._Lookup(Id, true, true) 370 } 371 372 // Lookup messages in index. 373 // Gets: slice of message ids to get. 374 // Returns slice of MsgInfo pointers. 375 // Does lock! 376 func (db *DB) LookupIDS(Ids []string) []*MsgInfo { 377 var info []*MsgInfo 378 db.Sync.RLock() 379 defer db.Sync.RUnlock() 380 db.Lock() 381 defer db.Unlock() 382 for _, id := range Ids { 383 i := db._Lookup(id, false, true) 384 if i != nil { 385 info = append(info, i) 386 } 387 } 388 return info 389 } 390 391 // Internal function. Gets bundle by message id. 392 // If idx is true: load/create index. 393 // Returns: msgid:base64 bundle. 394 // Does not lock! 395 func (db *DB) _GetBundle(Id string, idx bool) (string, *MsgInfo) { 396 info := db._Lookup(Id, false, idx) 397 if info == nil { 398 Info.Printf("Can not find bundle: %s\n", Id) 399 return "", nil 400 } 401 f, err := os.Open(db.BundlePath()) 402 if err != nil { 403 Error.Printf("Can not open DB: %s\n", err) 404 return "", nil 405 } 406 defer f.Close() 407 _, err = f.Seek(info.Off, 0) 408 if err != nil { 409 Error.Printf("Can not seek DB: %s\n", err) 410 return "", nil 411 } 412 var bundle string 413 err = f_lines(f, func(line string) bool { 414 bundle = line 415 return false 416 }) 417 if err != nil { 418 Error.Printf("Can not get %s from DB: %s\n", Id, err) 419 return "", nil 420 } 421 return bundle, info 422 } 423 424 // Get bundle line by message id from db. 425 // Does lock! 426 // Loads/create index if needed. 427 func (db *DB) GetBundle(Id string) string { 428 db.Sync.RLock() 429 defer db.Sync.RUnlock() 430 db.Lock() 431 defer db.Unlock() 432 433 b, _ := db._GetBundle(Id, true) 434 return b 435 } 436 437 func (db *DB) GetBundleInfo(Id string) (string, *MsgInfo) { 438 db.Sync.RLock() 439 defer db.Sync.RUnlock() 440 db.Lock() 441 defer db.Unlock() 442 443 return db._GetBundle(Id, true) 444 } 445 446 // Get decoded message from db by message id. 447 // Does lock. Loads/create index if needed. 448 func (db *DB) Get(Id string) *Msg { 449 bundle := db.GetBundle(Id) 450 if bundle == "" { 451 return nil 452 } 453 m, err := DecodeBundle(bundle) 454 if err != nil { 455 Error.Printf("Can not decode bundle on get: %s\n", Id) 456 } 457 return m 458 } 459 460 // Fast varian (w/o locking) of Get. 461 // Get decoded message from db by message id. 462 // Does NOT lock! Loads/create index if needed. 463 func (db *DB) GetFast(Id string) *Msg { 464 bundle, _ := db._GetBundle(Id, false) 465 if bundle == "" { 466 return nil 467 } 468 m, err := DecodeBundle(bundle) 469 if err != nil { 470 Error.Printf("Can not decode bundle on get: %s\n", Id) 471 } 472 return m 473 } 474 475 // Query used to make queries to Index 476 // If some field of: Echo, Repto, From, To is not "" 477 // fields will be matched with MsgInfo entry (logical AND). 478 // If Match function is not nil, this function will be used for matching. 479 // Blacklisted: search in blacklisted messages if true. 480 // User: authorized access to private areas. 481 // Start & Lim: slice of query. For example: -1, 1 -- get last message in db. 0, 1 -- first. 482 type Query struct { 483 Echo string 484 Repto string 485 From string 486 To string 487 Start int 488 Lim int 489 Blacklisted bool 490 User User 491 Match func(mi *MsgInfo, q Query) bool 492 } 493 494 // utility function to add string in front of slice 495 func prependStr(x []string, y string) []string { 496 x = append(x, "") 497 copy(x[1:], x) 498 x[0] = y 499 return x 500 } 501 502 // Check if message is private 503 func (db *DB) Access(info *MsgInfo, user *User) bool { 504 if IsPrivate(info.Echo) { 505 if user.Name == "" { 506 return false 507 } 508 if info.To != "All" && info.From != user.Name && info.To != user.Name { 509 return false 510 } 511 } 512 return true 513 } 514 515 // Default match function for queries. 516 func (db *DB) Match(info *MsgInfo, r Query) bool { 517 if r.Blacklisted { 518 if info.Off >= 0 { 519 return false 520 } 521 } else if info.Off < 0 { 522 return false 523 } 524 if r.Echo != "" && r.Echo != info.Echo { 525 return false 526 } 527 if r.Repto == "!" { 528 if info.Repto != "" { 529 return false 530 } 531 } else if r.Repto != "" && r.Repto != info.Repto { 532 return false 533 } 534 if r.To != "" && r.To != info.To { 535 return false 536 } 537 if r.From != "" && r.From != info.From { 538 return false 539 } 540 if !db.Access(info, &r.User) { 541 return false 542 } 543 if r.Match != nil { 544 return r.Match(info, r) 545 } 546 return true 547 } 548 549 // Used to get information about echoarea 550 // Count: number of messages 551 // Topics: number of topics 552 // Last: last MsgInfo 553 // Msg: last message pointer 554 type Echo struct { 555 Name string 556 Count int 557 Topics int 558 Last *MsgInfo 559 Msg *Msg 560 } 561 562 // Make query and select Echoes 563 // Returns: slice of pointers to Echo. 564 // names: if not empty, lookup only in theese echoareas 565 // Does lock. 566 // Load/create index if needed. 567 // Echoes sorted by date of last messages. 568 func (db *DB) Echoes(names []string, q Query) []*Echo { 569 db.Sync.Lock() 570 defer db.Sync.Unlock() 571 db.Lock() 572 defer db.Unlock() 573 var list []*Echo 574 575 filter := make(map[string]bool) 576 for _, n := range names { 577 filter[n] = true 578 } 579 580 if err := db.LoadIndex(); err != nil { 581 return list 582 } 583 584 db.IdxSync.RLock() 585 defer db.IdxSync.RUnlock() 586 587 hash := make(map[string]Echo) 588 size := len(db.Idx.List) 589 for i := 0; i < size; i++ { 590 id := db.Idx.List[i] 591 info := db.Idx.Hash[id] 592 if info.Off < 0 { 593 continue 594 } 595 if !db.Match(info, q) { 596 continue 597 } 598 e := info.Echo 599 if names != nil { // filter? 600 if _, ok := filter[e]; !ok { 601 continue 602 } 603 } 604 if v, ok := hash[e]; ok { 605 if info.Repto == "" { 606 v.Topics++ 607 } 608 v.Count++ 609 v.Last = info 610 hash[e] = v 611 } else { 612 v := Echo{Name: e, Count: 1, Last: info} 613 if info.Repto == "" { 614 v.Topics = 1 615 } 616 hash[e] = v 617 } 618 } 619 if names != nil { 620 for _, v := range names { 621 n := hash[v] 622 list = append(list, &n) 623 } 624 } else { 625 for _, v := range hash { 626 n := v 627 list = append(list, &n) 628 } 629 } 630 for _, v := range list { 631 v.Msg = db.GetFast(v.Last.Id) 632 if v.Msg == nil { 633 Error.Printf("Can not get echo last message: %s", v.Last.Id) 634 v.Msg = &Msg{} 635 } 636 } 637 sort.SliceStable(list, func(i, j int) bool { 638 return list[i].Msg.Date > list[j].Msg.Date 639 }) 640 return list 641 } 642 643 // Make query and retuen ids as slice of strings. 644 // Does lock. Can create/load index if needed. 645 // r: request, see Query 646 func (db *DB) SelectIDS(r Query) []string { 647 var Resp []string 648 db.Sync.Lock() 649 defer db.Sync.Unlock() 650 db.Lock() 651 defer db.Unlock() 652 653 if err := db.LoadIndex(); err != nil { 654 return Resp 655 } 656 size := len(db.Idx.List) 657 if size == 0 { 658 return Resp 659 } 660 661 db.IdxSync.RLock() 662 defer db.IdxSync.RUnlock() 663 664 if r.Start < 0 { 665 start := 0 666 for i := size - 1; i >= 0; i-- { 667 id := db.Idx.List[i] 668 if db.Match(db.Idx.Hash[id], r) { 669 Resp = prependStr(Resp, id) 670 start -= 1 671 if start == r.Start { 672 break 673 } 674 } 675 } 676 if r.Lim > 0 && len(Resp) > r.Lim { 677 Resp = Resp[0:r.Lim] 678 } 679 return Resp 680 } 681 found := 0 682 for i := r.Start; i < size; i++ { 683 id := db.Idx.List[i] 684 if db.Match(db.Idx.Hash[id], r) { 685 Resp = append(Resp, id) 686 found += 1 687 if r.Lim > 0 && found == r.Lim { 688 break 689 } 690 } 691 } 692 return Resp 693 } 694 695 // Internal function. Get slice of MsgInfo pointers 696 // and create information about topics. 697 // Information returns in form of: [topicid][]ids 698 // topic id is the msg id of most old parent in echo 699 // ids - is the messages in this topic 700 func (db *DB) GetTopics(mi []*MsgInfo) map[string][]string { 701 db.Sync.RLock() 702 defer db.Sync.RUnlock() 703 704 intopic := make(map[string]string) 705 topics := make(map[string][]string) 706 707 if err := db.LoadIndex(); err != nil { 708 // FIXME: handle this error 709 panic(err) 710 } 711 for _, m := range mi { 712 if _, ok := intopic[m.Id]; ok { 713 continue 714 } 715 var l []*MsgInfo 716 if m.Topic != "" { // fast path 717 if len(topics[m.Topic]) == 0 { 718 topics[m.Topic] = append(topics[m.Topic], m.Topic) 719 } 720 if m.Id != m.Topic { 721 topics[m.Topic] = append(topics[m.Topic], m.Id) 722 intopic[m.Id] = m.Topic 723 } 724 continue 725 } 726 for p := m; p != nil; p = db.LookupFast(p.Repto, false) { 727 if p.Repto == p.Id || p.Topic == "visited" { // loop? 728 p.Topic = "" 729 break 730 } 731 if m.Echo != p.Echo { 732 continue 733 } 734 if p.Topic == "" { 735 p.Topic = "visited" 736 } 737 l = append(l, p) 738 } 739 if len(l) == 0 { 740 continue 741 } 742 t := l[len(l)-1] 743 if len(topics[t.Id]) == 0 { 744 topics[t.Id] = append(topics[t.Id], t.Id) 745 } 746 sort.SliceStable(l, func(i int, j int) bool { 747 return l[i].Num < l[j].Num 748 }) 749 for _, i := range l { 750 if i.Id == t.Id { 751 i.Topic = t.Id 752 continue 753 } 754 if _, ok := intopic[i.Id]; ok { 755 continue 756 } 757 topics[t.Id] = append(topics[t.Id], i.Id) 758 intopic[i.Id] = t.Id 759 i.Topic = t.Id 760 } 761 } 762 763 return topics 764 } 765 766 // Store decoded message in database 767 // If message exists, returns error 768 func (db *DB) Store(m *Msg) error { 769 if r, _ := m.Tag("repto"); r == "" { // new one! 770 if m.Echo == "std.hugeping" && m.Addr != "ping,1" { 771 return errors.New("Access denied") 772 } 773 } 774 return db._Store(m, false) 775 } 776 777 // Store decoded message in database 778 // even it is exists. So, it's like Edit operation. 779 // While index loaded, it got last version of message data. 780 func (db *DB) Edit(m *Msg) error { 781 return db._Store(m, true) 782 } 783 784 // Blacklist decoded message. 785 // Blacklisting is adding special tag: access/blacklist and Edit operation 786 // to store it in DB. While loading index, blacklisted messages 787 // are marked by negative Off field (-1). 788 func (db *DB) Blacklist(m *Msg) error { 789 if err := m.Tags.Add("access/blacklist"); err != nil { 790 return err 791 } 792 return db.Edit(m) 793 794 //db.Sync.Lock() 795 //defer db.Sync.Unlock() 796 //db.Lock() 797 //defer db.Unlock() 798 // repto, _ := m.Tag("repto") 799 // if repto != "" { 800 // repto = ":" + repto 801 // } 802 // rec := fmt.Sprintf("%s:%s:%d%s", m.MsgId, m.Echo, -1, repto) 803 // if err := append_file(db.IndexPath(), rec); err != nil { 804 // return err 805 // } 806 // return nil 807 } 808 809 // Internal function used by Store. See Store comment. 810 func (db *DB) _Store(m *Msg, edit bool) error { 811 db.Sync.Lock() 812 defer db.Sync.Unlock() 813 db.Lock() 814 defer db.Unlock() 815 repto, _ := m.Tag("repto") 816 if err := db.LoadIndex(); err != nil { 817 return err 818 } 819 820 db.IdxSync.RLock() 821 defer db.IdxSync.RUnlock() 822 823 if _, ok := db.Idx.Hash[m.MsgId]; ok && !edit { // exist and not edit 824 return errors.New("Already exists") 825 } 826 // if repto != "" { 827 // if _, ok := db.Idx.Hash[repto]; !ok { // repto is absent, we should avoid loops! 828 // return errors.New("Wrong repto: " + repto) 829 // } 830 // } 831 fi, err := os.Stat(db.BundlePath()) 832 var off int64 833 if err == nil { 834 off = fi.Size() 835 } 836 if v, _ := m.Tag("access"); v == "blacklist" { 837 off = -1 838 } 839 if err := append_file(db.BundlePath(), m.Encode()); err != nil { 840 return err 841 } 842 843 rec := fmt.Sprintf("%s:%s:%d:%s:%s:%s", m.MsgId, m.Echo, off, m.To, m.From, repto) 844 if err := append_file(db.IndexPath(), rec); err != nil { 845 return err 846 } 847 return nil 848 } 849 850 // Opens DB and returns pointer to DB object. 851 // path is the path to db. By default it is ./db 852 // Index will be named as path + ".idx" 853 func OpenDB(path string) *DB { 854 var db DB 855 db.Path = path 856 info, err := os.Stat(filepath.Dir(path)) 857 if err != nil || !info.IsDir() { 858 return nil 859 } 860 db.Name = "node" 861 // db.Idx = make(map[string]Index) 862 return &db 863 } 864 865 // User entry in points.txt db 866 // User with Id == 1 is superuser. 867 // Tags: custom information (like avatars :) in Tags format 868 type User struct { 869 Id int32 870 Name string 871 Mail string 872 Secret string 873 Token string 874 Tags Tags 875 } 876 877 // User database. 878 // ModTime: last modification time of points.txt to detect DB changes. 879 // FileSize - size of points.txt to detect DB changes. 880 // Names: holds User structure by user name 881 // ById: holds user name by user id 882 // Tokens: holds user name by user token 883 // List: holds user names as list 884 type UDB struct { 885 Path string 886 Names map[string]User 887 ById map[int32]string 888 Tokens map[string]string 889 List []string 890 Sync sync.RWMutex 891 ModTime int64 892 FileSize int64 893 } 894 895 // Check username if it is valid 896 func IsUsername(u string) bool { 897 return !strings.ContainsAny(u, ":\n\r\t/") && 898 !strings.HasPrefix(u, " ") && 899 !strings.HasSuffix(u, " ") && 900 len(u) <= 16 && len(u) > 2 901 } 902 903 // Check password if it is valid to be used 904 func IsPassword(u string) bool { 905 return len(u) >= 1 906 } 907 908 // Make secret from string. 909 // String is something like id + user + password 910 func MakeSecret(msg string) string { 911 h := sha256.Sum256([]byte(msg)) 912 hash, err := bcrypt.GenerateFromPassword(h[:], bcrypt.DefaultCost) 913 if err != nil { 914 Error.Printf("bcrypt problem") 915 return "bcryptProblem" 916 } 917 return string(hash) 918 } 919 920 // Return token for username or "" if no such user 921 func (db *UDB) Token(User string) string { 922 db.Sync.RLock() 923 defer db.Sync.RUnlock() 924 ui, ok := db.Names[User] 925 if !ok { 926 return "" 927 } 928 return ui.Token 929 } 930 931 // Returns true if user+password is valid 932 func (db *UDB) Auth(User string, Passwd string) bool { 933 db.Sync.RLock() 934 defer db.Sync.RUnlock() 935 ui, ok := db.Names[User] 936 if !ok { 937 return false 938 } 939 locked, _ := ui.Tags.Get("locked") 940 if locked == "" { 941 Error.Printf("Can't get locked tag (%s)", User) 942 return false 943 } 944 if locked != "no" { 945 Info.Printf("Login locked user attempt (%s)", User) 946 return false 947 } 948 secret := sha256.Sum256([]byte(User+Passwd)) 949 return bcrypt.CompareHashAndPassword([]byte(ui.Secret), secret[:]) == nil 950 } 951 952 // Returns true if token is valid 953 func (db *UDB) Access(Token string) bool { 954 db.Sync.RLock() 955 defer db.Sync.RUnlock() 956 _, ok := db.Tokens[Token] 957 return ok 958 } 959 960 // Return username for given Token 961 func (db *UDB) Name(Token string) string { 962 db.Sync.RLock() 963 defer db.Sync.RUnlock() 964 name, ok := db.Tokens[Token] 965 if ok { 966 return name 967 } 968 Error.Printf("No user for Token: %s", Token) 969 return "" 970 } 971 972 // Return User pointer for given Token 973 func (db *UDB) UserInfo(Token string) *User { 974 db.Sync.RLock() 975 defer db.Sync.RUnlock() 976 name, ok := db.Tokens[Token] 977 if ok { 978 v := db.Names[name] 979 return &v 980 } 981 Error.Printf("No user for token: %s", Token) 982 return nil 983 } 984 985 // Return User pointer for user id 986 func (db *UDB) UserInfoId(id int32) *User { 987 db.Sync.RLock() 988 defer db.Sync.RUnlock() 989 name, ok := db.ById[id] 990 if ok { 991 v := db.Names[name] 992 return &v 993 } 994 Error.Printf("No user for Id: %d", id) 995 return nil 996 } 997 998 // Return User pointer for given user name 999 func (db *UDB) UserInfoName(name string) *User { 1000 db.Sync.RLock() 1001 defer db.Sync.RUnlock() 1002 v, ok := db.Names[name] 1003 if ok { 1004 return &v 1005 } 1006 return nil 1007 } 1008 1009 // Return user id for given token 1010 func (db *UDB) Id(token string) int32 { 1011 db.Sync.RLock() 1012 defer db.Sync.RUnlock() 1013 name, ok := db.Tokens[token] 1014 if ok { 1015 v, ok := db.Names[name] 1016 if !ok { 1017 return -1 1018 } 1019 return v.Id 1020 } 1021 Error.Printf("No user for token: %s", token) 1022 return -1 1023 } 1024 1025 var emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") 1026 1027 // Add (register) user in database 1028 // Mail is optional but someday it will be used in registration process 1029 func (db *UDB) Add(Name string, Mail string, Passwd string) error { 1030 db.Sync.Lock() 1031 defer db.Sync.Unlock() 1032 1033 if _, ok := db.Names[Name]; ok { 1034 return errors.New("User already exists") 1035 } 1036 if !IsUsername(Name) { 1037 return errors.New("Wrong username") 1038 } 1039 if !IsPassword(Passwd) { 1040 return errors.New("Bad password") 1041 } 1042 if !emailRegex.MatchString(Mail) { 1043 return errors.New("Wrong email") 1044 } 1045 var id int32 = 0 1046 for _, v := range db.Names { 1047 if v.Id > id { 1048 id = v.Id 1049 } 1050 } 1051 id++ 1052 var u User 1053 u.Name = Name 1054 u.Mail = Mail 1055 u.Secret = MakeSecret(Name + Passwd) 1056 u.Tags = NewTags("locked/yes") 1057 db.List = append(db.List, u.Name) 1058 if err := append_file(db.Path, fmt.Sprintf("%d:%s:%s:%s:%s", 1059 id, Name, Mail, u.Secret, u.Tags.String())); err != nil { 1060 return err 1061 } 1062 return nil 1063 } 1064 1065 // Open user database and return pointer to UDB object 1066 func OpenUsers(path string) *UDB { 1067 var db UDB 1068 db.Path = path 1069 return &db 1070 } 1071 1072 // Change (replace) information about user. 1073 // Gets pointer to User object and write it in DB, replacing old information. 1074 // Works atomically using rename. 1075 func (db *UDB) Edit(u *User) error { 1076 db.Sync.Lock() 1077 defer db.Sync.Unlock() 1078 if _, ok := db.Names[u.Name]; !ok { 1079 return errors.New("No such user") 1080 } 1081 db.Names[u.Name] = *u // new version 1082 os.Remove(db.Path + ".tmp") 1083 for _, Name := range db.List { 1084 ui := db.Names[Name] 1085 if err := append_file(db.Path+".tmp", fmt.Sprintf("%d:%s:%s:%s:%s", 1086 ui.Id, Name, ui.Mail, ui.Secret, ui.Tags.String())); err != nil { 1087 return err 1088 } 1089 } 1090 if err := os.Rename(db.Path+".tmp", db.Path); err != nil { 1091 return err 1092 } 1093 db.ModTime = 0 // force to reload 1094 return nil 1095 } 1096 1097 // Load user information in memory if it is needed (ModTime or FileSize changed). 1098 // So, it is safe to call it on every request. 1099 func (db *UDB) LoadUsers() error { 1100 db.Sync.Lock() 1101 defer db.Sync.Unlock() 1102 var mtime int64 1103 var fsize int64 1104 file, err := os.Open(db.Path) 1105 if err == nil { 1106 info, err := file.Stat() 1107 file.Close() 1108 if err != nil { 1109 Error.Printf("Can not stat %s file: %s", db.Path, err) 1110 return err 1111 } 1112 mtime = info.ModTime().Unix() 1113 fsize = info.Size() 1114 } else if os.IsNotExist(err) { 1115 mtime = 0 1116 } else { 1117 Error.Printf("Can not open %s file: %s", db.Path, err) 1118 return err 1119 } 1120 if db.ModTime == mtime && db.FileSize == fsize { 1121 return nil 1122 } 1123 // save old tokens before reload 1124 old_tokens := make(map[string]string) 1125 for otoken, oname := range db.Tokens { 1126 old_tokens[oname] = otoken 1127 } 1128 db.Names = make(map[string]User) 1129 db.Tokens = make(map[string]string) 1130 db.ById = make(map[int32]string) 1131 db.List = nil 1132 err = FileLines(db.Path, func(line string) bool { 1133 a := strings.Split(line, ":") 1134 if len(a) < 4 { 1135 Error.Printf("Wrong entry in user DB: %s", line) 1136 return true 1137 } 1138 var u User 1139 var err error 1140 _, err = fmt.Sscanf(a[0], "%d", &u.Id) 1141 if err != nil { 1142 Error.Printf("Wrong ID in user DB: %s", a[0]) 1143 return true 1144 } 1145 u.Name = a[1] 1146 u.Mail = a[2] 1147 u.Secret = a[3] 1148 u.Tags = NewTags(a[4]) 1149 //restore token if user onlocked 1150 token, ok := old_tokens[u.Name] 1151 if ok { 1152 locked, _ := u.Tags.Get("locked") 1153 if locked != "" && locked == "no" { 1154 u.Token = token 1155 db.Tokens[token] = u.Name 1156 } 1157 } 1158 db.ById[u.Id] = u.Name 1159 db.Names[u.Name] = u 1160 db.List = append(db.List, u.Name) 1161 return true 1162 }) 1163 if err != nil { 1164 Error.Printf("Can not read user DB: %s", err) 1165 return errors.New(err.Error()) 1166 } 1167 db.ModTime = mtime 1168 db.FileSize = fsize 1169 return nil 1170 } 1171 1172 // Echo database entry 1173 // Holds echo descriptions in Info hash. 1174 // List - names of echoareas. 1175 type EDB struct { 1176 Info map[string]string 1177 List []string 1178 Path string 1179 } 1180 1181 // Check if echo is exists in echo database 1182 func (db *EDB) Allowed(name string) bool { 1183 if len(db.List) == 0 { 1184 return true 1185 } 1186 if _, ok := db.Info[name]; ok { 1187 return true 1188 } 1189 return false 1190 } 1191 1192 // Loads echolist database and returns pointer to EDB 1193 // Supposed to be called only once 1194 func LoadEcholist(path string) *EDB { 1195 var db EDB 1196 db.Path = path 1197 db.Info = make(map[string]string) 1198 1199 err := FileLines(path, func(line string) bool { 1200 a := strings.SplitN(line, ":", 3) 1201 if len(a) < 2 { 1202 Error.Printf("Wrong entry in echo DB: %s", line) 1203 return true 1204 } 1205 db.Info[a[0]] = a[2] 1206 db.List = append(db.List, a[0]) 1207 return true 1208 }) 1209 if err != nil { 1210 Error.Printf("Can not read echo DB: %s", err) 1211 return nil 1212 } 1213 return &db 1214 }