Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/bradfitz/gomemcache

go 1.18
go 1.22.1
114 changes: 113 additions & 1 deletion memcache/memcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ var (

// ErrNoServers is returned when no servers are configured or available.
ErrNoServers = errors.New("memcache: no servers configured or available")

// ErrNotAuthenticated is returned when Client Authentication failed due to any reason.
ErrNotAuthenticated = errors.New("memcache: Client Authentication Failed")
)

const (
Expand Down Expand Up @@ -114,6 +117,20 @@ var (

resultClientErrorPrefix = []byte("CLIENT_ERROR ")
versionPrefix = []byte("VERSION")

// Authentication Related Error
resultUnauthenticatedError = []byte("CLIENT_ERROR unauthenticated\r\n")
resultAuthenticationFailure = []byte("CLIENT_ERROR authentication failure\r\n")
resultBadCommandFormat = []byte("CLIENT_ERROR bad command line format\r\n")
resultBadCommandFormatTermination = []byte("CLIENT_ERROR bad command line format termination\r\n")
resultBadAuthenticationTokenFormat = []byte("CLIENT_ERROR bad authentication token format\r\n")

// Authentication Error Stuffs
resultUnauthenticatedError = []byte("CLIENT_ERROR unauthenticated\r\n")
resultAuthenticationFailure = []byte("CLIENT_ERROR authentication failure\r\n")
resultBadCommandFormat = []byte("CLIENT_ERROR bad command line format\r\n")
resultBadCommandFormatTermination = []byte("CLIENT_ERROR bad command line format termination\r\n")
resultBadAuthenticationTokenFormat = []byte("CLIENT_ERROR bad authentication token format\r\n")
)

// New returns a memcache client using the provided server(s)
Expand Down Expand Up @@ -181,6 +198,12 @@ type Item struct {
// It's populated by get requests and then the same value is
// required for a CompareAndSwap request to succeed.
CasID uint64

// Username for Authentication
User string

// Password for Authentication
Pass string
}

// conn is a connection to a server.
Expand Down Expand Up @@ -320,7 +343,7 @@ func (c *Client) onItem(item *Item, fn func(*Client, *bufio.ReadWriter, *Item) e
return err
}
defer cn.condRelease(&err)
if err = fn(c, cn.rw, item); err != nil {
if err := fn(c, cn.rw, item); err != nil {
return err
}
return nil
Expand Down Expand Up @@ -847,3 +870,92 @@ func (c *Client) Close() error {
c.freeconn = nil
return ret
}

// Memcached Authentication Support

func (c *Client) SetAuth(item *Item) error {
return c.onItem(item, (*Client).setAuth)
}

func (c *Client) setAuth(rw *bufio.ReadWriter, item *Item) error {
return c.authFunc(rw, "set", item)
}

func (c *Client) authFunc(rw *bufio.ReadWriter, verb string, item *Item) error {
if !legalKey(item.Key) {
return ErrMalformedKey
}
var err error
_, err = fmt.Fprintf(rw, "%s %s %d %d %d\r\n%s %s\r\n",
verb, item.Key, item.Flags, item.Expiration, len(item.User)+len(item.Pass)+1, item.User, item.Pass)
if err != nil {
return err
}
if err := rw.Flush(); err != nil {
return err
}
line, err := rw.ReadSlice('\n')

if err != nil {
return err
}
switch {
case bytes.Equal(line, resultStored):
return nil
case bytes.Equal(line, resultUnauthenticatedError):
return ErrNotAuthenticated
case bytes.Equal(line, resultAuthenticationFailure):
return ErrNotAuthenticated
case bytes.Equal(line, resultBadCommandFormat):
return ErrNotAuthenticated
case bytes.Equal(line, resultBadCommandFormatTermination):
return ErrNotAuthenticated
case bytes.Equal(line, resultBadAuthenticationTokenFormat):
return ErrNotAuthenticated
}
return fmt.Errorf("memcache: unexpected response line from %q: %q", verb, string(line))
}

// Memcached Authentication Support
func (c *Client) SetAuth(item *Item) error {
return c.onItem(item, (*Client).setAuth)
}

func (c *Client) setAuth(rw *bufio.ReadWriter, item *Item) error {
return c.authFunc(rw, "set", item)
}

func (c *Client) authFunc(rw *bufio.ReadWriter, verb string, item *Item) error {
if !legalKey(item.Key) {
return ErrMalformedKey
}
var err error
_, err = fmt.Fprintf(rw, "%s %s %d %d %d\r\n%s %s\r\n",
verb, item.Key, item.Flags, item.Expiration, len(item.User)+len(item.Pass)+1, item.User, item.Pass)
if err != nil {
return err
}
if err := rw.Flush(); err != nil {
return err
}
line, err := rw.ReadSlice('\n')

if err != nil {
return err
}
switch {
case bytes.Equal(line, resultStored):
return nil
case bytes.Equal(line, resultUnauthenticatedError):
return ErrNotAuthenticated
case bytes.Equal(line, resultAuthenticationFailure):
return ErrNotAuthenticated
case bytes.Equal(line, resultBadCommandFormat):
return ErrNotAuthenticated
case bytes.Equal(line, resultBadCommandFormatTermination):
return ErrNotAuthenticated
case bytes.Equal(line, resultBadAuthenticationTokenFormat):
return ErrNotAuthenticated
}
return fmt.Errorf("memcache: unexpected response line from %q: %q", verb, string(line))
}