Makes error reporting easier.
"crypto/aes"
"crypto/cipher"
"crypto/rand"
- "errors"
+ "github.com/Kovensky/go-anidb/udp"
"io"
"runtime"
)
}
}
-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)
}
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()
import (
"encoding/gob"
"github.com/Kovensky/go-anidb/udp"
+ "log"
+ "sync"
"time"
)
type udpWrap struct {
*udpapi.AniDBUDP
+ sendLock sync.Mutex
sendQueueCh chan paramSet
+ credLock sync.Mutex
credentials *credentials
connected bool
}
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 {
if wait > time.Minute {
wait = time.Minute
}
+
time.Sleep(wait)
goto Retry
}
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
}
}
}
+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
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,
// 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,
"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.
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])
// Yes, AniDB works in ECB mode
a.ecb = newECBState(udpKey, salt)
}
- return reply.Error()
}
+ return reply
}