From a457ecff137f07dbdf7f321e86b266a71db91fc1 Mon Sep 17 00:00:00 2001 From: "Diogo Franco (Kovensky)" Date: Tue, 16 Jul 2013 13:43:45 -0300 Subject: [PATCH] udp, anidb: Lock shared data, return APIReply for authentication Makes error reporting easier. --- auth.go | 56 +++++++++++++++++++++++++++++++++++++++++++---------- udp.go | 44 +++++++++++++++++++++++++++++++++++------ udp/auth.go | 27 +++++++++++++------------- 3 files changed, 97 insertions(+), 30 deletions(-) diff --git a/auth.go b/auth.go index a4058a9..029d5a1 100644 --- a/auth.go +++ b/auth.go @@ -4,7 +4,7 @@ import ( "crypto/aes" "crypto/cipher" "crypto/rand" - "errors" + "github.com/Kovensky/go-anidb/udp" "io" "runtime" ) @@ -83,23 +83,49 @@ func newCredentials(username, password, udpKey string) *credentials { } } -func (udp *udpWrap) ReAuth() error { - if c := udp.credentials; c != nil { - defer runtime.GC() // any better way to clean the plaintexts? +func (udp *udpWrap) ReAuth() udpapi.APIReply { + if Banned() { + return bannedReply + } + + udp.credLock.Lock() + defer udp.credLock.Unlock() - udp.connected = true - return udp.Auth( + if c := udp.credentials; c != nil { + r := udp.AniDBUDP.Auth( decrypt(c.username), decrypt(c.password), decrypt(c.udpKey)) + runtime.GC() // any better way to clean the plaintexts? + + err := r.Error() + + if err != nil { + switch r.Code() { + // 555 -- banned + // 601 -- server down, treat the same as a ban + case 555, 601: + setBanned() + case 500: // bad credentials + udp.credentials.shred() + udp.credentials = nil + case 503, 504: // client rejected + panic(err) + } + } + udp.connected = err == nil + return r } - return errors.New("No credentials stored") + return &noauthAPIReply{} } // Saves the used credentials in the AniDB struct, to allow automatic // re-authentication when needed; they are (properly) encrypted with a key that's // uniquely generated every time the module is initialized. func (adb *AniDB) SetCredentials(username, password, udpKey string) { + adb.udp.credLock.Lock() + defer adb.udp.credLock.Unlock() + adb.udp.credentials.shred() adb.udp.credentials = newCredentials(username, password, udpKey) } @@ -110,18 +136,28 @@ func (adb *AniDB) SetCredentials(username, password, udpKey string) { func (adb *AniDB) Auth(username, password, udpKey string) (err error) { defer runtime.GC() // any better way to clean the plaintexts? - if err = adb.udp.Auth(username, password, udpKey); err == nil { - adb.udp.connected = true + adb.udp.sendLock.Lock() + defer adb.udp.sendLock.Unlock() + + if !Banned() { adb.SetCredentials(username, password, udpKey) } - return + + // ReAuth clears the credentials if they're bad + return adb.udp.ReAuth().Error() } // Logs the user out and removes the credentials from the AniDB struct. func (adb *AniDB) Logout() error { + adb.udp.credLock.Lock() + defer adb.udp.credLock.Unlock() + adb.udp.credentials.shred() adb.udp.credentials = nil + adb.udp.sendLock.Lock() + defer adb.udp.sendLock.Unlock() + if adb.udp.connected { adb.udp.connected = false return adb.udp.Logout() diff --git a/udp.go b/udp.go index 84cd5c8..372aead 100644 --- a/udp.go +++ b/udp.go @@ -3,6 +3,8 @@ package anidb import ( "encoding/gob" "github.com/Kovensky/go-anidb/udp" + "log" + "sync" "time" ) @@ -46,8 +48,10 @@ type paramSet struct { type udpWrap struct { *udpapi.AniDBUDP + sendLock sync.Mutex sendQueueCh chan paramSet + credLock sync.Mutex credentials *credentials connected bool } @@ -100,6 +104,12 @@ func (udp *udpWrap) sendQueue() { wait := initialWait for set := range udp.sendQueueCh { Retry: + if Banned() { + set.ch <- bannedReply + close(set.ch) + continue + } + reply := <-udp.AniDBUDP.SendRecv(set.cmd, udpapi.ParamMap(set.params)) if reply.Error() == udpapi.TimeoutError { @@ -108,6 +118,7 @@ func (udp *udpWrap) sendQueue() { if wait > time.Minute { wait = time.Minute } + time.Sleep(wait) goto Retry } @@ -115,7 +126,7 @@ func (udp *udpWrap) sendQueue() { switch reply.Code() { case 403, 501, 506: // not logged in, or session expired - if err := udp.ReAuth(); err == nil { + if r := udp.ReAuth(); r.Error() == nil { // retry goto Retry } @@ -129,13 +140,26 @@ func (udp *udpWrap) sendQueue() { } } +type errorReply struct { + udpapi.APIReply + err error +} + +func (r *errorReply) Code() int { + return 999 +} +func (r *errorReply) Text() string { + return r.err.Error() +} +func (r *errorReply) Error() error { + return r.err +} + func (udp *udpWrap) SendRecv(cmd string, params paramMap) <-chan udpapi.APIReply { ch := make(chan udpapi.APIReply, 1) - if udp.credentials == nil { - ch <- &noauthAPIReply{} - close(ch) - return ch - } + + udp.sendLock.Lock() + defer udp.sendLock.Unlock() if Banned() { ch <- bannedReply @@ -143,6 +167,14 @@ func (udp *udpWrap) SendRecv(cmd string, params paramMap) <-chan udpapi.APIReply return ch } + if !udp.connected { + if r := udp.ReAuth(); r.Error() != nil { + ch <- r + close(ch) + return ch + } + } + udp.sendQueueCh <- paramSet{ cmd: cmd, params: params, diff --git a/udp/auth.go b/udp/auth.go index c78e800..48e8527 100755 --- a/udp/auth.go +++ b/udp/auth.go @@ -13,20 +13,20 @@ import ( // http://wiki.anidb.net/w/UDP_API_Definition#AUTH:_Authing_to_the_AnimeDB // // http://wiki.anidb.net/w/UDP_API_Definition#ENCRYPT:_Start_Encrypted_Session -func (a *AniDBUDP) Auth(user, password, udpKey string) (err error) { +func (a *AniDBUDP) Auth(user, password, udpKey string) (reply APIReply) { if a.session != "" { - if err = (<-a.Uptime()).Error(); err == nil { - return nil + if reply = <-a.Uptime(); reply.Error() == nil { + return reply } } a.session = "" if udpKey != "" { - if err = a.encrypt(user, udpKey); err != nil { - return err + if reply = a.encrypt(user, udpKey); reply.Error() != nil { + return reply } } - r := <-a.SendRecv("AUTH", ParamMap{ + reply = <-a.SendRecv("AUTH", ParamMap{ "user": user, "pass": password, "protover": 3, @@ -36,12 +36,12 @@ func (a *AniDBUDP) Auth(user, password, udpKey string) (err error) { "comp": 1, "enc": "UTF-8", }) - switch r.Code() { + switch reply.Code() { case 200, 201: - f := strings.Fields(r.Text()) + f := strings.Fields(reply.Text()) a.session = f[0] } - return r.Error() + return reply } // Ends the API session. Blocks until we have confirmation. @@ -53,10 +53,9 @@ func (a *AniDBUDP) Logout() (err error) { return r.Error() } -func (a *AniDBUDP) encrypt(user, udpKey string) (err error) { - if reply := <-a.SendRecv("ENCRYPT", ParamMap{"user": user, "type": 1}); reply.Error() != nil { - return reply.Error() - } else { +func (a *AniDBUDP) encrypt(user, udpKey string) (reply APIReply) { + a.ecb = nil + if reply = <-a.SendRecv("ENCRYPT", ParamMap{"user": user, "type": 1}); reply.Error() == nil { switch reply.Code() { case 209: salt := []byte(strings.Fields(reply.Text())[0]) @@ -64,6 +63,6 @@ func (a *AniDBUDP) encrypt(user, udpKey string) (err error) { // Yes, AniDB works in ECB mode a.ecb = newECBState(udpKey, salt) } - return reply.Error() } + return reply } -- 2.44.0