7 "github.com/Kovensky/go-anidb/udp"
12 // We still have the key and IV somewhere in memory...
13 // but it's better than plaintext.
14 type credentials struct {
20 func (c *credentials) shred() {
22 io.ReadFull(rand.Reader, c.username)
23 io.ReadFull(rand.Reader, c.password)
24 io.ReadFull(rand.Reader, c.udpKey)
31 // Randomly generated on every execution
35 aesKey = make([]byte, aes.BlockSize)
36 if _, err := io.ReadFull(rand.Reader, aesKey); err != nil {
41 func crypt(plaintext string) []byte {
42 p := []byte(plaintext)
44 block, err := aes.NewCipher(aesKey)
49 ciphertext := make([]byte, len(p)+aes.BlockSize)
50 iv := ciphertext[:aes.BlockSize]
51 if _, err := io.ReadFull(rand.Reader, iv); err != nil {
55 stream := cipher.NewCTR(block, iv)
56 stream.XORKeyStream(ciphertext[aes.BlockSize:], p)
61 func decrypt(ciphertext []byte) string {
62 if len(ciphertext) <= aes.BlockSize {
65 p := make([]byte, len(ciphertext)-aes.BlockSize)
67 block, err := aes.NewCipher(aesKey)
72 stream := cipher.NewCTR(block, ciphertext[:aes.BlockSize])
73 stream.XORKeyStream(p, ciphertext[aes.BlockSize:])
78 func newCredentials(username, password, udpKey string) *credentials {
80 username: crypt(username),
81 password: crypt(password),
82 udpKey: crypt(udpKey),
86 func (udp *udpWrap) ReAuth() udpapi.APIReply {
92 defer udp.credLock.Unlock()
94 if c := udp.credentials; c != nil {
95 logRequest(paramSet{cmd: "AUTH", params: paramMap{"user": decrypt(c.username)}})
96 r := udp.AniDBUDP.Auth(
107 // 601 -- server down, treat the same as a ban
110 case 500: // bad credentials
111 udp.credentials.shred()
112 udp.credentials = nil
113 case 503, 504: // client rejected
117 udp.connected = err == nil
120 // We can't use SendRecv here as it would deadlock
121 ch := make(chan udpapi.APIReply, 1)
122 udp.sendQueueCh <- paramSet{
124 params: paramMap{"user": decrypt(c.username)},
130 uid, _ := parseUserReply(reply)
131 udp.user = uid.User()
138 return &noauthAPIReply{}
141 // Saves the used credentials in the AniDB struct, to allow automatic
142 // re-authentication when needed; they are (properly) encrypted with a key that's
143 // uniquely generated every time the module is initialized.
144 func (adb *AniDB) SetCredentials(username, password, udpKey string) {
145 adb.udp.credLock.Lock()
146 defer adb.udp.credLock.Unlock()
148 adb.udp.credentials.shred()
149 adb.udp.credentials = newCredentials(username, password, udpKey)
152 // Authenticates to anidb's UDP API and, on success, stores the credentials using
153 // SetCredentials. If udpKey is not "", the communication with the server
154 // will be encrypted, but in the VERY weak ECB mode.
155 func (adb *AniDB) Auth(username, password, udpKey string) (err error) {
156 defer runtime.GC() // any better way to clean the plaintexts?
158 adb.udp.sendLock.Lock()
159 defer adb.udp.sendLock.Unlock()
162 adb.SetCredentials(username, password, udpKey)
165 // ReAuth clears the credentials if they're bad
166 return adb.udp.ReAuth().Error()
169 // Logs the user out and removes the credentials from the AniDB struct.
170 func (adb *AniDB) Logout() error {
171 adb.udp.credLock.Lock()
172 defer adb.udp.credLock.Unlock()
174 adb.udp.credentials.shred()
175 adb.udp.credentials = nil
177 adb.udp.sendLock.Lock()
178 defer adb.udp.sendLock.Unlock()
182 if adb.udp.connected {
183 adb.udp.connected = false
184 logRequest(paramSet{cmd: "LOGOUT"})
185 return adb.udp.Logout()