{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -fno-warn-unused-binds #-}

-- | SASL authentication support for memcached.
module Database.Memcache.SASL (
        authenticate, Username, Password
    ) where

import Database.Memcache.Errors
import Database.Memcache.Server
import Database.Memcache.Types

import Control.Monad
import Data.ByteString
import qualified Data.ByteString.Char8 as BC
import Data.Monoid

-- | Username for authentication.
type Username = ByteString

-- | Password for authentication.
type Password = ByteString

-- | Perform SASL authentication with the server.
authenticate :: Connection -> Username -> Password -> IO Bool
-- NOTE: For correctness really should check that PLAIN auth is supported first
-- but we'll just assume it is as that's all mainline and other implementations
-- support and one exception is nearly as good as another.
authenticate = saslAuthPlain

-- | Perform SASL PLAIN authentication.
saslAuthPlain :: Connection -> Username -> Password -> IO Bool
saslAuthPlain c u p = do
    let credentials = singleton 0 <> u <> singleton 0 <> p
        msg = emptyReq { reqOp = ReqSASLStart (BC.pack "PLAIN") credentials }
    r <- sendRecv c msg
    when (resOp r /= ResSASLStart) $ throwIncorrectRes r "SASL_START"
    case resStatus r of
        NoError      -> return True
        SaslAuthFail -> return False
        _            -> throwStatus r

-- | List available SASL authentication methods. We could call this but as we
-- only support PLAIN as does the memcached server, we simply assume PLAIN
-- authentication is supprted and try that.
saslListMechs :: Connection -> IO ByteString
saslListMechs c = do
    let msg = emptyReq { reqOp = ReqSASLList }
    r <- sendRecv c msg
    v <- case resOp r of
        ResSASLList v -> return v
        _             -> throwIncorrectRes r "SASL_LIST"
    case resStatus r of
        NoError        -> return v
        _              -> throwStatus r