Skip to content

Commit c2f5270

Browse files
committed
Revert "Remove LOGIN server"
This reverts commit b788ff2.
1 parent b788ff2 commit c2f5270

File tree

2 files changed

+144
-0
lines changed

2 files changed

+144
-0
lines changed

login.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,54 @@ func (a *loginClient) Next(challenge []byte) (response []byte, err error) {
3636
func NewLoginClient(username, password string) Client {
3737
return &loginClient{username, password}
3838
}
39+
40+
// Authenticates users with an username and a password.
41+
type LoginAuthenticator func(username, password string) error
42+
43+
type loginState int
44+
45+
const (
46+
loginNotStarted loginState = iota
47+
loginWaitingUsername
48+
loginWaitingPassword
49+
)
50+
51+
type loginServer struct {
52+
state loginState
53+
username, password string
54+
authenticate LoginAuthenticator
55+
}
56+
57+
// A server implementation of the LOGIN authentication mechanism, as described
58+
// in https://tools.ietf.org/html/draft-murchison-sasl-login-00.
59+
//
60+
// LOGIN is obsolete and should only be enabled for legacy clients that cannot
61+
// be updated to use PLAIN.
62+
func NewLoginServer(authenticator LoginAuthenticator) Server {
63+
return &loginServer{authenticate: authenticator}
64+
}
65+
66+
func (a *loginServer) Next(response []byte) (challenge []byte, done bool, err error) {
67+
switch a.state {
68+
case loginNotStarted:
69+
// Check for initial response field, as per RFC4422 section 3
70+
if response == nil {
71+
challenge = []byte("Username:")
72+
break
73+
}
74+
a.state++
75+
fallthrough
76+
case loginWaitingUsername:
77+
a.username = string(response)
78+
challenge = []byte("Password:")
79+
case loginWaitingPassword:
80+
a.password = string(response)
81+
err = a.authenticate(a.username, a.password)
82+
done = true
83+
default:
84+
err = ErrUnexpectedClientResponse
85+
}
86+
87+
a.state++
88+
return
89+
}

login_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package sasl_test
22

33
import (
44
"bytes"
5+
"errors"
56
"testing"
67

78
"github.com/emersion/go-sasl"
@@ -34,3 +35,95 @@ func TestNewLoginClient(t *testing.T) {
3435
t.Error("Invalid initial response:", resp)
3536
}
3637
}
38+
39+
func TestNewLoginServer(t *testing.T) {
40+
var authenticated = false
41+
s := sasl.NewLoginServer(func(username, password string) error {
42+
if username != "tim" {
43+
return errors.New("Invalid username: " + username)
44+
}
45+
if password != "tanstaaftanstaaf" {
46+
return errors.New("Invalid password: " + password)
47+
}
48+
49+
authenticated = true
50+
return nil
51+
})
52+
53+
challenge, done, err := s.Next(nil)
54+
if err != nil {
55+
t.Fatal("Error while starting server:", err)
56+
}
57+
if done {
58+
t.Fatal("Done after starting server")
59+
}
60+
if string(challenge) != "Username:" {
61+
t.Error("Invalid first challenge:", challenge)
62+
}
63+
64+
challenge, done, err = s.Next([]byte("tim"))
65+
if err != nil {
66+
t.Fatal("Error while sending username:", err)
67+
}
68+
if done {
69+
t.Fatal("Done after sending username")
70+
}
71+
if string(challenge) != "Password:" {
72+
t.Error("Invalid challenge after sending username:", challenge)
73+
}
74+
75+
challenge, done, err = s.Next([]byte("tanstaaftanstaaf"))
76+
if err != nil {
77+
t.Fatal("Error while sending password:", err)
78+
}
79+
if !done {
80+
t.Fatal("Authentication not finished after sending password")
81+
}
82+
if len(challenge) > 0 {
83+
t.Error("Invalid non-empty final challenge:", challenge)
84+
}
85+
86+
if !authenticated {
87+
t.Error("Not authenticated")
88+
}
89+
90+
// Tests with initial response field, as per RFC4422 section 3
91+
authenticated = false
92+
s = sasl.NewLoginServer(func(username, password string) error {
93+
if username != "tim" {
94+
return errors.New("Invalid username: " + username)
95+
}
96+
if password != "tanstaaftanstaaf" {
97+
return errors.New("Invalid password: " + password)
98+
}
99+
100+
authenticated = true
101+
return nil
102+
})
103+
104+
challenge, done, err = s.Next([]byte("tim"))
105+
if err != nil {
106+
t.Fatal("Error while sending username:", err)
107+
}
108+
if done {
109+
t.Fatal("Done after sending username")
110+
}
111+
if string(challenge) != "Password:" {
112+
t.Error("Invalid challenge after sending username:", string(challenge))
113+
}
114+
115+
challenge, done, err = s.Next([]byte("tanstaaftanstaaf"))
116+
if err != nil {
117+
t.Fatal("Error while sending password:", err)
118+
}
119+
if !done {
120+
t.Fatal("Authentication not finished after sending password")
121+
}
122+
if len(challenge) > 0 {
123+
t.Error("Invalid non-empty final challenge:", challenge)
124+
}
125+
126+
if !authenticated {
127+
t.Error("Not authenticated")
128+
}
129+
}

0 commit comments

Comments
 (0)