]> git.lizzy.rs Git - go-anidb.git/commitdiff
udp, anidb: Lock shared data, return APIReply for authentication
authorDiogo Franco (Kovensky) <diogomfranco@gmail.com>
Tue, 16 Jul 2013 16:43:45 +0000 (13:43 -0300)
committerDiogo Franco (Kovensky) <diogomfranco@gmail.com>
Tue, 16 Jul 2013 16:43:45 +0000 (13:43 -0300)
Makes error reporting easier.

auth.go
udp.go
udp/auth.go

diff --git a/auth.go b/auth.go
index a4058a9854643e30e1ac2f27b06b3218a5a0499d..029d5a1f05f8050bf28a8d17001a5416831c2a42 100644 (file)
--- 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 84cd5c8124b0c0ad3b6e2f73e37c591738f4d4e9..372aeadf20d8afc2117608f236f9614351a7a4b4 100644 (file)
--- 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,
index c78e8001eba9a633233aff072c86770136888121..48e8527cdbca9bb6d2254f8135419d4e96914770 100755 (executable)
@@ -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
 }