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

{-|
Module      : Database.Memcache.SASL
Description : SASL Authentication
Copyright   : (c) David Terei, 2016
License     : BSD
Maintainer  : code@davidterei.com
Stability   : stable
Portability : GHC

SASL authentication support for Memcached.
-}
module Database.Memcache.SASL (
        -- * Types
        Authentication(..), Username, Password,

        -- * Operations
        authenticate
    ) where

import Database.Memcache.Errors
import Database.Memcache.Socket
import Database.Memcache.Types

import Control.Exception (throwIO)
import Control.Monad
import Data.ByteString.Char8 as B8 (ByteString, pack, singleton)
import Data.Monoid

-- | Perform SASL authentication with the server.
authenticate :: Socket -> Authentication -> IO ()
{-# INLINE authenticate #-}
authenticate _ NoAuth     = return ()
authenticate s (Auth u p) = saslAuthPlain s u p
-- 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.

-- | Perform SASL PLAIN authentication.
saslAuthPlain :: Socket -> Username -> Password -> IO ()
{-# INLINE saslAuthPlain #-}
saslAuthPlain s u p = do
    let credentials = singleton '\0' <> u <> singleton '\0' <> p
        msg = emptyReq { reqOp = ReqSASLStart (B8.pack "PLAIN") credentials }
    send s msg
    r <- recv s
    when (resOp r /= ResSASLStart) $
        throwIO $ wrongOp r "SASL_START"
    case resStatus r of
        NoError -> return ()
        rs      -> throwIO $ OpError rs

-- | 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 :: Socket -> IO B8.ByteString
{-# INLINE saslListMechs #-}
saslListMechs s = do
    let msg = emptyReq { reqOp = ReqSASLList }
    send s msg
    r <- recv s
    v <- case resOp r of
        ResSASLList v -> return v
        _             -> throwIO $ wrongOp r "SASL_LIST"
    case resStatus r of
        NoError -> return v
        rs      -> throwIO $ OpError rs