diff --git a/go.mod b/go.mod index 1bf1615..8ee417c 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/bradfitz/gomemcache -go 1.18 +go 1.22.1 diff --git a/memcache/memcache.go b/memcache/memcache.go index 6f48caa..b6d3a31 100644 --- a/memcache/memcache.go +++ b/memcache/memcache.go @@ -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 ( @@ -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) @@ -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. @@ -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 @@ -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)) +} \ No newline at end of file