-- Copyright (C) 2010 John Millikin -- -- This program is free software: you can redistribute it and/or modify -- it under the terms of the GNU General Public License as published by -- the Free Software Foundation, either version 3 of the License, or -- any later version. -- -- This program is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU General Public License for more details. -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see . module Network.Protocol.SASL.GNU ( -- * Library Information headerVersion , libraryVersion , checkVersion -- * SASL Contexts , SASL , runSASL , setCallback , runCallback -- * Mechanisms , Mechanism (..) , clientMechanisms , clientSupports , clientSuggestMechanism , serverMechanisms , serverSupports -- * SASL Sessions , Session , runClient , runServer , mechanismName -- ** Session Properties , Property (..) , setProperty , getProperty , getPropertyFast -- ** Session IO , Progress (..) , step , step64 , encode , decode -- ** Error handling , Error (..) , catch , handle , try , throw -- * Bundled codecs , toBase64 , fromBase64 , md5 , sha1 , hmacMD5 , hmacSHA1 , nonce , random ) where -- Imports {{{ import Prelude hiding (catch) import Data.Maybe (fromMaybe) import Control.Applicative (Applicative, pure, (<*>), (<$>)) import qualified Control.Exception as E import Control.Monad (ap, when, unless, (<=<)) import Control.Monad.Loops (unfoldrM) import Control.Monad.IO.Class (MonadIO, liftIO) import qualified Control.Monad.Trans.Reader as R import qualified Data.ByteString as B import qualified Data.ByteString.Unsafe as B import qualified Data.ByteString.Char8 as Char8 import Data.Char (isDigit) import Data.String (IsString, fromString) import qualified Foreign as F import qualified Foreign.C as F import System.IO.Unsafe (unsafePerformIO) import qualified Text.ParserCombinators.ReadP as P -- }}} -- Library Information {{{ -- | Which version of @gsasl.h@ this module was compiled against headerVersion :: (Integer, Integer, Integer) headerVersion = (major, minor, patch) where major = toInteger hsgsasl_VERSION_MAJOR minor = toInteger hsgsasl_VERSION_MINOR patch = toInteger hsgsasl_VERSION_PATCH -- | Which version of @libgsasl.so@ is loaded libraryVersion :: IO (Integer, Integer, Integer) libraryVersion = io where parseVersion str = case P.readP_to_S parser str of [] -> Nothing ((parsed, _):_) -> Just parsed parser = do majorS <- P.munch1 isDigit _ <- P.char '.' minorS <- P.munch1 isDigit _ <- P.char '.' patchS <- P.munch1 isDigit eof return (read majorS, read minorS, read patchS) io = do cstr <- gsasl_check_version F.nullPtr maybeStr <- F.maybePeek F.peekCString cstr return $ fromMaybe (error $ "Invalid version string: " ++ show maybeStr) (maybeStr >>= parseVersion) eof = do s <- P.look unless (null s) P.pfail -- | Whether the header and library versions are compatible checkVersion :: IO Bool checkVersion = fmap (== 1) hsgsasl_check_version -- }}} -- SASL Contexts {{{ newtype Context = Context (F.Ptr Context) newtype SASL a = SASL { unSASL :: R.ReaderT Context IO a } instance Functor SASL where fmap f = SASL . fmap f . unSASL instance Applicative SASL where pure = SASL . pure (<*>) = ap instance Monad SASL where return = SASL . return (>>=) sasl f = SASL $ unSASL sasl >>= unSASL . f instance MonadIO SASL where liftIO = SASL . liftIO -- TODO: more instances runSASL :: SASL a -> IO a runSASL = withContext . R.runReaderT . unSASL withContext :: (Context -> IO a) -> IO a withContext = E.bracket newContext freeContext where newContext = F.alloca $ \pCtxt -> do gsasl_init pCtxt >>= checkRC Context `fmap` F.peek pCtxt freeContext (Context ctx) = do hook <- gsasl_callback_hook_get ctx gsasl_done ctx freeCallbackHook hook getContext :: SASL (F.Ptr Context) getContext = SASL $ do Context ptr <- R.ask return ptr bracketSASL :: (F.Ptr Context -> IO a) -> (a -> IO b) -> (a -> IO c) -> SASL c bracketSASL before after thing = do ctx <- getContext liftIO $ E.bracket (before ctx) after thing -- }}} -- Mechanisms {{{ newtype Mechanism = Mechanism B.ByteString deriving (Show, Eq) instance IsString Mechanism where fromString = Mechanism . fromString -- | A list of 'Mechanism's supported by the @libgsasl@ client. clientMechanisms :: SASL [Mechanism] clientMechanisms = bracketSASL io gsasl_free splitMechListPtr where io ctx = F.alloca $ \pStr -> do gsasl_client_mechlist ctx pStr >>= checkRC F.peek pStr -- | Whether there is client-side support for a specified 'Mechanism'. clientSupports :: Mechanism -> SASL Bool clientSupports (Mechanism name) = do ctx <- getContext liftIO $ B.useAsCString name $ \pName -> do cres <- gsasl_client_support_p ctx pName return $ cres == 1 -- | Given a list of 'Mechanism's, suggest which to use (or 'Nothing' if -- no supported 'Mechanism' is found). clientSuggestMechanism :: [Mechanism] -> SASL (Maybe Mechanism) clientSuggestMechanism mechs = do let bytes = B.intercalate (Char8.pack " ") [x | Mechanism x <- mechs] ctx <- getContext liftIO $ B.useAsCString bytes $ F.maybePeek (fmap Mechanism . B.packCString) <=< gsasl_client_suggest_mechanism ctx -- | A list of 'Mechanism's supported by the @libgsasl@ server. serverMechanisms :: SASL [Mechanism] serverMechanisms = bracketSASL io gsasl_free splitMechListPtr where io ctx = F.alloca $ \pStr -> do gsasl_server_mechlist ctx pStr >>= checkRC F.peek pStr -- | Whether there is server-side support for a specified 'Mechanism'. serverSupports :: Mechanism -> SASL Bool serverSupports (Mechanism name) = do ctx <- getContext liftIO $ B.useAsCString name $ \pName -> do cres <- gsasl_server_support_p ctx pName return $ cres == 1 splitMechListPtr :: F.CString -> IO [Mechanism] splitMechListPtr ptr = unfoldrM step' (ptr, ptr, 0, True) where step' (_, _, _, False) = return Nothing step' (p_0, p_i, i, _) = F.peek p_i >>= \chr -> let p_i' = F.plusPtr p_i 1 peek continue = if i == 0 then step' (p_i', p_i', 0, continue) else do bytes <- B.packCStringLen (p_0, i) return $ Just (Mechanism bytes, (p_i', p_i', 0, continue)) in case chr of 0x00 -> peek False 0x20 -> peek True _ -> step' (p_0, p_i', i + 1, True) -- }}} -- SASL Sessions {{{ newtype SessionCtx = SessionCtx (F.Ptr SessionCtx) newtype Session a = Session { unSession :: R.ReaderT SessionCtx IO a } instance Functor Session where fmap f = Session . fmap f . unSession instance Applicative Session where pure = Session . pure (<*>) = ap instance Monad Session where return = Session . return (>>=) m f = Session $ unSession m >>= unSession . f instance MonadIO Session where liftIO = Session . liftIO type SessionProc = F.Ptr Context -> F.CString -> F.Ptr (F.Ptr SessionCtx) -> IO F.CInt runSession :: SessionProc -> Mechanism -> Session a -> SASL (Either Error a) runSession start (Mechanism mech) session = bracketSASL newSession freeSession io where newSession ctx = B.useAsCString mech $ \pMech -> F.alloca $ \pSessionCtx -> E.handle noSession $ do start ctx pMech pSessionCtx >>= checkRC Right . SessionCtx <$> F.peek pSessionCtx noSession (SASLException err) = return $ Left err freeSession (Left _) = return () freeSession (Right (SessionCtx ptr)) = gsasl_finish ptr io (Left err) = return $ Left err io (Right sctx) = E.catch (Right <$> R.runReaderT (unSession session) sctx) (\(SASLException err) -> return $ Left err) -- | Run a session using the @libgsasl@ client. runClient :: Mechanism -> Session a -> SASL (Either Error a) runClient = runSession gsasl_client_start -- | Run a session using the @libgsasl@ server. runServer :: Mechanism -> Session a -> SASL (Either Error a) runServer = runSession gsasl_server_start getSessionContext :: Session (F.Ptr SessionCtx) getSessionContext = Session $ do SessionCtx sctx <- R.ask return sctx -- | The name of the session's SASL mechanism. mechanismName :: Session Mechanism mechanismName = do sctx <- getSessionContext liftIO $ do cstr <- gsasl_mechanism_name sctx Mechanism `fmap` B.packCString cstr bracketSession :: (F.Ptr SessionCtx -> IO a) -> (a -> IO b) -> (a -> IO c) -> Session c bracketSession before after thing = do sctx <- getSessionContext liftIO $ E.bracket (before sctx) after thing -- }}} -- Error handling {{{ data Error = UnknownMechanism | MechanismCalledTooManyTimes | MallocError | Base64Error | CryptoError | SASLPrepError | MechanismParseError | AuthenticationError | IntegrityError | NoClientCode | NoServerCode | NoCallback | NoAnonymousToken | NoAuthID | NoAuthzID | NoPassword | NoPasscode | NoPIN | NoService | NoHostname | GSSAPI_ReleaseBufferError | GSSAPI_ImportNameError | GSSAPI_InitSecContextError | GSSAPI_AcceptSecContextError | GSSAPI_UnwrapError | GSSAPI_WrapError | GSSAPI_AquireCredError | GSSAPI_DisplayNameError | GSSAPI_UnsupportedProtectionError | GSSAPI_EncapsulateTokenError | GSSAPI_DecapsulateTokenError | GSSAPI_InquireMechForSASLNameError | GSSAPI_TestOIDSetMemberError | GSSAPI_ReleaseOIDSetError | KerberosV5_InitError | KerberosV5_InternalError | SecurID_ServerNeedAdditionalPasscode | SecurID_ServerNeedNewPIN instance Show Error where show = strError -- | Convert an error code to a human-readable string explanation for the -- particular error code. -- -- This string can be used to output a diagnostic message to the user. strError :: Error -> String strError err = unsafePerformIO $ gsasl_strerror (cFromError err) >>= F.peekCString newtype SASLException = SASLException Error deriving (Show) instance E.Exception SASLException cFromError :: Error -> F.CInt cFromError e = case e of UnknownMechanism -> 2 MechanismCalledTooManyTimes -> 3 MallocError -> 7 Base64Error -> 8 CryptoError -> 9 SASLPrepError -> 29 MechanismParseError -> 30 AuthenticationError -> 31 IntegrityError -> 33 NoClientCode -> 35 NoServerCode -> 36 NoCallback -> 51 NoAnonymousToken -> 52 NoAuthID -> 53 NoAuthzID -> 54 NoPassword -> 55 NoPasscode -> 56 NoPIN -> 57 NoService -> 58 NoHostname -> 59 GSSAPI_ReleaseBufferError -> 37 GSSAPI_ImportNameError -> 38 GSSAPI_InitSecContextError -> 39 GSSAPI_AcceptSecContextError -> 40 GSSAPI_UnwrapError -> 41 GSSAPI_WrapError -> 42 GSSAPI_AquireCredError -> 43 GSSAPI_DisplayNameError -> 44 GSSAPI_UnsupportedProtectionError -> 45 GSSAPI_EncapsulateTokenError -> 60 GSSAPI_DecapsulateTokenError -> 61 GSSAPI_InquireMechForSASLNameError -> 62 GSSAPI_TestOIDSetMemberError -> 63 GSSAPI_ReleaseOIDSetError -> 64 KerberosV5_InitError -> 46 KerberosV5_InternalError -> 47 SecurID_ServerNeedAdditionalPasscode -> 48 SecurID_ServerNeedNewPIN -> 49 cToError :: F.CInt -> Error cToError x = case x of 2 -> UnknownMechanism 3 -> MechanismCalledTooManyTimes 7 -> MallocError 8 -> Base64Error 9 -> CryptoError 29 -> SASLPrepError 30 -> MechanismParseError 31 -> AuthenticationError 33 -> IntegrityError 35 -> NoClientCode 36 -> NoServerCode 51 -> NoCallback 52 -> NoAnonymousToken 53 -> NoAuthID 54 -> NoAuthzID 55 -> NoPassword 56 -> NoPasscode 57 -> NoPIN 58 -> NoService 59 -> NoHostname 37 -> GSSAPI_ReleaseBufferError 38 -> GSSAPI_ImportNameError 39 -> GSSAPI_InitSecContextError 40 -> GSSAPI_AcceptSecContextError 41 -> GSSAPI_UnwrapError 42 -> GSSAPI_WrapError 43 -> GSSAPI_AquireCredError 44 -> GSSAPI_DisplayNameError 45 -> GSSAPI_UnsupportedProtectionError 60 -> GSSAPI_EncapsulateTokenError 61 -> GSSAPI_DecapsulateTokenError 62 -> GSSAPI_InquireMechForSASLNameError 63 -> GSSAPI_TestOIDSetMemberError 64 -> GSSAPI_ReleaseOIDSetError 46 -> KerberosV5_InitError 47 -> KerberosV5_InternalError 48 -> SecurID_ServerNeedAdditionalPasscode 49 -> SecurID_ServerNeedNewPIN _ -> error $ "Unknown GNU SASL return code: " ++ show x throw :: Error -> Session a throw = liftIO . E.throwIO . SASLException catch :: Session a -> (Error -> Session a) -> Session a catch m f = do sctx <- SessionCtx `fmap` getSessionContext Session . liftIO $ E.catch (R.runReaderT (unSession m) sctx) (\(SASLException err) -> R.runReaderT (unSession (f err)) sctx) handle :: (Error -> Session a) -> Session a -> Session a handle = flip catch try :: Session a -> Session (Either Error a) try m = catch (fmap Right m) (return . Left) -- }}} -- Session Properties {{{ data Property = PropertyAuthID | PropertyAuthzID | PropertyPassword | PropertyAnonymousToken | PropertyService | PropertyHostname | PropertyGSSAPIDisplayName | PropertyPasscode | PropertySuggestedPIN | PropertyPIN | PropertyRealm | PropertyDigestMD5HashedPassword | PropertyQOPS | PropertyQOP | PropertyScramIter | PropertyScramSalt | PropertyScramSaltedPassword | ValidateSimple | ValidateExternal | ValidateAnonymous | ValidateGSSAPI | ValidateSecurID deriving (Show, Eq) cFromProperty :: Property -> F.CInt cFromProperty x = case x of PropertyAuthID -> 1 PropertyAuthzID -> 2 PropertyPassword -> 3 PropertyAnonymousToken -> 4 PropertyService -> 5 PropertyHostname -> 6 PropertyGSSAPIDisplayName -> 7 PropertyPasscode -> 8 PropertySuggestedPIN -> 9 PropertyPIN -> 10 PropertyRealm -> 11 PropertyDigestMD5HashedPassword -> 12 PropertyQOPS -> 13 PropertyQOP -> 14 PropertyScramIter -> 15 PropertyScramSalt -> 16 PropertyScramSaltedPassword -> 17 ValidateSimple -> 500 ValidateExternal -> 501 ValidateAnonymous -> 502 ValidateGSSAPI -> 503 ValidateSecurID -> 504 cToProperty :: F.CInt -> Property cToProperty x = case x of 1 -> PropertyAuthID 2 -> PropertyAuthzID 3 -> PropertyPassword 4 -> PropertyAnonymousToken 5 -> PropertyService 6 -> PropertyHostname 7 -> PropertyGSSAPIDisplayName 8 -> PropertyPasscode 9 -> PropertySuggestedPIN 10 -> PropertyPIN 11 -> PropertyRealm 12 -> PropertyDigestMD5HashedPassword 13 -> PropertyQOPS 14 -> PropertyQOP 15 -> PropertyScramIter 16 -> PropertyScramSalt 17 -> PropertyScramSaltedPassword 500 -> ValidateSimple 501 -> ValidateExternal 502 -> ValidateAnonymous 503 -> ValidateGSSAPI 504 -> ValidateSecurID _ -> error $ "Unknown GNU SASL property code: " ++ show x -- | Store some data in the session for the given property. The data must -- be @NULL@-terminated. setProperty :: Property -> B.ByteString -> Session () setProperty prop bytes = do sctx <- getSessionContext liftIO $ B.useAsCString bytes $ gsasl_property_set sctx (cFromProperty prop) -- | Retrieve the data stored in the session for the given property, -- possibly invoking the current callback to get the value. getProperty :: Property -> Session (Maybe B.ByteString) getProperty prop = do sctx <- getSessionContext liftIO $ do cstr <- gsasl_property_get sctx (cFromProperty prop) if cstr /= F.nullPtr then Just <$> B.packCString cstr else do liftIO $ checkCallbackException sctx return Nothing -- | Retrieve the data stored in the session for the given property, -- without invoking the current callback. getPropertyFast :: Property -> Session (Maybe B.ByteString) getPropertyFast prop = do sctx <- getSessionContext liftIO $ gsasl_property_fast sctx (cFromProperty prop) >>= F.maybePeek B.packCString -- }}} -- Callbacks {{{ type CallbackFn = F.Ptr Context -> F.Ptr SessionCtx -> F.CInt -> IO F.CInt data CallbackHook = CallbackHook (F.FunPtr CallbackFn) (Property -> Session Progress) newCallbackHook :: (Property -> Session Progress) -> IO (F.Ptr CallbackHook, F.FunPtr CallbackFn) newCallbackHook cb = E.bracketOnError (wrapCallbackImpl (callbackImpl cb)) F.freeHaskellFunPtr (\funPtr -> let hook = CallbackHook funPtr cb in E.bracketOnError (F.newStablePtr hook) F.freeStablePtr (\stablePtr -> let hookPtr = F.castPtr (F.castStablePtrToPtr stablePtr) in return (hookPtr, funPtr))) freeCallbackHook :: F.Ptr CallbackHook -> IO () freeCallbackHook ptr = unless (ptr == F.nullPtr) $ do let stablePtr = F.castPtrToStablePtr $ F.castPtr ptr hook <- F.deRefStablePtr stablePtr F.freeStablePtr stablePtr let (CallbackHook funPtr _) = hook F.freeHaskellFunPtr funPtr callbackImpl :: (Property -> Session Progress) -> CallbackFn callbackImpl cb _ sctx cProp = let globalIO = error "globalIO is not implemented" sessionIO = do let session = cb $ cToProperty cProp cFromProgress <$> R.runReaderT (unSession session) (SessionCtx sctx) onError :: SASLException -> IO F.CInt onError (SASLException err) = return $ cFromError err onException :: E.SomeException -> IO F.CInt onException exc = do -- A bit ugly; session hooks aren't used anywhere else in -- the binding, so the exception is stashed here. stablePtr <- F.newStablePtr exc gsasl_session_hook_set sctx $ F.castStablePtrToPtr stablePtr -- standard libgsasl return codes are all >= 0, so using -1 -- provides an easy way to determine later whether the -- exception came from Haskell code. return (-1) catchErrors io = E.catches io [E.Handler onError, E.Handler onException] in catchErrors $ if sctx == F.nullPtr then globalIO else sessionIO foreign import ccall "wrapper" wrapCallbackImpl :: CallbackFn -> IO (F.FunPtr CallbackFn) -- Used to check whether a callback threw an exception checkCallbackException :: F.Ptr SessionCtx -> IO () checkCallbackException sctx = do hook <- gsasl_session_hook_get sctx when (hook /= F.nullPtr) $ do let stable = F.castPtrToStablePtr hook exc <- F.deRefStablePtr stable F.freeStablePtr stable E.throwIO (exc :: E.SomeException) -- | Set the current SASL callback. The callback will be used by mechanisms -- to discover various parameters, such as usernames and passwords. setCallback :: (Property -> Session Progress) -> SASL () setCallback cb = do ctx <- getContext liftIO $ do freeCallbackHook =<< gsasl_callback_hook_get ctx (hook, cbPtr) <- newCallbackHook cb gsasl_callback_hook_set ctx hook gsasl_callback_set ctx cbPtr -- | Run the current callback; the property indicates what action the -- callback is expected to perform. runCallback :: Property -> Session Progress runCallback prop = do -- This is a bit evil; the first field in Gsasl_session is a Gsasl context, -- so it's safe to cast here (assuming they never change the layout). ctx <- fmap F.castPtr getSessionContext hookPtr <- liftIO $ gsasl_callback_hook_get ctx when (hookPtr == F.nullPtr) $ throw NoCallback hook <- liftIO $ F.deRefStablePtr $ F.castPtrToStablePtr hookPtr let (CallbackHook _ cb) = hook cb prop -- }}} -- Session IO {{{ data Progress = Complete | NeedsMore deriving (Show, Eq) cFromProgress :: Progress -> F.CInt cFromProgress x = case x of Complete -> 0 NeedsMore -> 1 -- | Perform one step of SASL authentication. This reads data from the other -- end, processes it (potentially running the callback), and returns data -- to be sent back. -- -- Also returns 'NeedsMore' if authentication is not yet complete. step :: B.ByteString -> Session (B.ByteString, Progress) step input = bracketSession get free peek where get sctx = B.unsafeUseAsCStringLen input $ \(pInput, inputLen) -> F.alloca $ \pOutput -> F.alloca $ \pOutputLen -> do rc <- gsasl_step sctx pInput (fromIntegral inputLen) pOutput pOutputLen when (rc /= 0) $ checkCallbackException sctx progress <- checkStepRC rc cstrLen <- F.peek pOutputLen cstr <- F.peek pOutput return (cstr, cstrLen, progress) free (cstr, _, _) = gsasl_free cstr peek (cstr, cstrLen, progress) = do output <- B.packCStringLen (cstr, fromIntegral cstrLen) return (output, progress) -- | A simple wrapper around 'step' which uses base64 to decode the input -- and encode the output. step64 :: B.ByteString -> Session (B.ByteString, Progress) step64 input = bracketSession get free peek where get sctx = B.useAsCString input $ \pInput -> F.alloca $ \pOutput -> do rc <- gsasl_step64 sctx pInput pOutput when (rc /= 0) $ checkCallbackException sctx progress <- checkStepRC rc cstr <- F.peek pOutput return (cstr, progress) free (cstr, _) = gsasl_free cstr peek (cstr, progress) = do output <- B.packCString cstr return (output, progress) checkStepRC :: F.CInt -> IO Progress checkStepRC x = case x of 0 -> return Complete 1 -> return NeedsMore _ -> E.throwIO (SASLException (cToError x)) encodeDecodeHelper :: (F.Storable a, Integral a, Num t) => (F.Ptr SessionCtx -> F.Ptr F.CChar -> t -> F.Ptr (F.Ptr F.CChar) -> F.Ptr a -> IO F.CInt) -> B.ByteString -> Session B.ByteString encodeDecodeHelper f input = do sctx <- getSessionContext liftIO $ B.unsafeUseAsCStringLen input $ \(cstr, cstrLen) -> F.alloca $ \pOutput -> F.alloca $ \pOutputLen -> do rc <- f sctx cstr (fromIntegral cstrLen) pOutput pOutputLen when (rc /= 0) $ checkCallbackException sctx checkRC rc output <- F.peek pOutput outputLen <- fromIntegral <$> F.peek pOutputLen outputBytes <- B.packCStringLen (output, outputLen) gsasl_free output return outputBytes -- | Encode data according to the negotiated SASL mechanism. This might mean -- the data is integrity or privacy protected. encode :: B.ByteString -> Session B.ByteString encode = encodeDecodeHelper gsasl_encode -- | Decode data according to the negotiated SASL mechanism. This might mean -- the data is integrity or privacy protected. decode :: B.ByteString -> Session B.ByteString decode = encodeDecodeHelper gsasl_decode -- }}} -- Bundled codecs {{{ base64Helper :: (F.Storable a, Integral a, Num t) => (F.Ptr F.CChar -> t -> F.Ptr (F.Ptr F.CChar) -> F.Ptr a -> IO F.CInt) -> B.ByteString -> B.ByteString base64Helper f input = unsafePerformIO $ B.unsafeUseAsCStringLen input $ \(pIn, inLen) -> F.alloca $ \pOut -> F.alloca $ \pOutLen -> do f pIn (fromIntegral inLen) pOut pOutLen >>= checkRC outLen <- F.peek pOutLen outPtr <- F.peek pOut B.packCStringLen (outPtr, fromIntegral outLen) toBase64 :: B.ByteString -> B.ByteString toBase64 = base64Helper gsasl_base64_to fromBase64 :: B.ByteString -> B.ByteString fromBase64 = base64Helper gsasl_base64_from md5 :: B.ByteString -> B.ByteString md5 input = unsafePerformIO $ B.unsafeUseAsCStringLen input $ \(pIn, inLen) -> F.alloca $ \pOut -> F.allocaBytes 16 $ \outBuf -> do F.poke pOut outBuf gsasl_md5 pIn (fromIntegral inLen) pOut >>= checkRC B.packCStringLen (outBuf, 16) sha1 :: B.ByteString -> B.ByteString sha1 input = unsafePerformIO $ B.unsafeUseAsCStringLen input $ \(pIn, inLen) -> F.alloca $ \pOut -> do gsasl_sha1 pIn (fromIntegral inLen) pOut >>= checkRC outBuf <- F.peek pOut ret <- B.packCStringLen (outBuf, 20) F.free outBuf return ret hmacMD5 :: B.ByteString -- ^ Key -> B.ByteString -- ^ Input data -> B.ByteString hmacMD5 key input = unsafePerformIO $ B.unsafeUseAsCStringLen key $ \(pKey, keyLen) -> B.unsafeUseAsCStringLen input $ \(pIn, inLen) -> F.alloca $ \pOut -> F.allocaBytes 16 $ \outBuf -> do F.poke pOut outBuf gsasl_hmac_md5 pKey (fromIntegral keyLen) pIn (fromIntegral inLen) pOut >>= checkRC B.packCStringLen (outBuf, 16) hmacSHA1 :: B.ByteString -- ^ Key -> B.ByteString -- ^ Input data -> B.ByteString hmacSHA1 key input = unsafePerformIO $ B.unsafeUseAsCStringLen key $ \(pKey, keyLen) -> B.unsafeUseAsCStringLen input $ \(pIn, inLen) -> F.alloca $ \pOut -> F.allocaBytes 20 $ \outBuf -> do F.poke pOut outBuf gsasl_hmac_sha1 pKey (fromIntegral keyLen) pIn (fromIntegral inLen) pOut >>= checkRC B.packCStringLen (outBuf, 20) -- | Returns unpredictable data of a given size nonce :: Integer -> IO B.ByteString nonce size = F.allocaBytes (fromInteger size) $ \buf -> do gsasl_nonce buf (fromIntegral size) >>= checkRC B.packCStringLen (buf, fromIntegral size) -- | Returns cryptographically strong random data of a given size random :: Integer -> IO B.ByteString random size = F.allocaBytes (fromInteger size) $ \buf -> do gsasl_random buf (fromIntegral size) >>= checkRC B.packCStringLen (buf, fromIntegral size) -- }}} -- Miscellaneous {{{ checkRC :: F.CInt -> IO () checkRC x = case x of 0 -> return () _ -> E.throwIO (SASLException (cToError x)) -- }}} -- FFI imports {{{ foreign import ccall "hsgsasl_VERSION_MAJOR" hsgsasl_VERSION_MAJOR :: F.CInt foreign import ccall "hsgsasl_VERSION_MINOR" hsgsasl_VERSION_MINOR :: F.CInt foreign import ccall "hsgsasl_VERSION_PATCH" hsgsasl_VERSION_PATCH :: F.CInt foreign import ccall "hsgsasl_check_version" hsgsasl_check_version :: IO F.CInt foreign import ccall "gsasl.h gsasl_init" gsasl_init :: F.Ptr (F.Ptr Context) -> IO F.CInt foreign import ccall "gsasl.h gsasl_done" gsasl_done :: F.Ptr Context -> IO () foreign import ccall "gsasl.h gsasl_check_version" gsasl_check_version :: F.CString -> IO F.CString foreign import ccall "gsasl.h gsasl_callback_set" gsasl_callback_set :: F.Ptr Context -> F.FunPtr CallbackFn -> IO () foreign import ccall "gsasl.h gsasl_callback_hook_get" gsasl_callback_hook_get :: F.Ptr Context -> IO (F.Ptr a) foreign import ccall "gsasl.h gsasl_callback_hook_set" gsasl_callback_hook_set :: F.Ptr Context -> F.Ptr a -> IO () foreign import ccall "gsasl.h gsasl_session_hook_get" gsasl_session_hook_get :: F.Ptr SessionCtx -> IO (F.Ptr a) foreign import ccall "gsasl.h gsasl_session_hook_set" gsasl_session_hook_set :: F.Ptr SessionCtx -> F.Ptr a -> IO () foreign import ccall "gsasl.h gsasl_property_set" gsasl_property_set :: F.Ptr SessionCtx -> F.CInt -> F.CString -> IO () foreign import ccall safe "gsasl.h gsasl_property_get" gsasl_property_get :: F.Ptr SessionCtx -> F.CInt -> IO F.CString foreign import ccall "gsasl.h gsasl_property_fast" gsasl_property_fast :: F.Ptr SessionCtx -> F.CInt -> IO F.CString foreign import ccall "gsasl.h gsasl_client_mechlist" gsasl_client_mechlist :: F.Ptr Context -> F.Ptr F.CString -> IO F.CInt foreign import ccall "gsasl.h gsasl_client_support_p" gsasl_client_support_p :: F.Ptr Context -> F.CString -> IO F.CInt foreign import ccall "gsasl.h gsasl_client_suggest_mechanism" gsasl_client_suggest_mechanism :: F.Ptr Context -> F.CString -> IO F.CString foreign import ccall "gsasl.h gsasl_server_mechlist" gsasl_server_mechlist :: F.Ptr Context -> F.Ptr F.CString -> IO F.CInt foreign import ccall "gsasl.h gsasl_server_support_p" gsasl_server_support_p :: F.Ptr Context -> F.CString -> IO F.CInt foreign import ccall safe "gsasl.h gsasl_client_start" gsasl_client_start :: SessionProc foreign import ccall safe "gsasl.h gsasl_server_start" gsasl_server_start :: SessionProc foreign import ccall safe "gsasl.h gsasl_step" gsasl_step :: F.Ptr SessionCtx -> F.CString -> F.CSize -> F.Ptr F.CString -> F.Ptr F.CSize -> IO F.CInt foreign import ccall safe "gsasl.h gsasl_step64" gsasl_step64 :: F.Ptr SessionCtx -> F.CString -> F.Ptr F.CString -> IO F.CInt foreign import ccall safe "gsasl.h gsasl_finish" gsasl_finish :: F.Ptr SessionCtx -> IO () foreign import ccall safe "gsasl.h gsasl_encode" gsasl_encode :: F.Ptr SessionCtx -> F.CString -> F.CSize -> F.Ptr F.CString -> F.Ptr F.CSize -> IO F.CInt foreign import ccall safe "gsasl.h gsasl_decode" gsasl_decode :: F.Ptr SessionCtx -> F.CString -> F.CSize -> F.Ptr F.CString -> F.Ptr F.CSize -> IO F.CInt foreign import ccall "gsasl.h gsasl_mechanism_name" gsasl_mechanism_name :: F.Ptr SessionCtx -> IO F.CString foreign import ccall "gsasl.h gsasl_strerror" gsasl_strerror :: F.CInt -> IO F.CString foreign import ccall "gsasl.h gsasl_base64_to" gsasl_base64_to :: F.CString -> F.CSize -> F.Ptr F.CString -> F.Ptr F.CSize -> IO F.CInt foreign import ccall "gsasl.h gsasl_base64_from" gsasl_base64_from :: F.CString -> F.CSize -> F.Ptr F.CString -> F.Ptr F.CSize -> IO F.CInt foreign import ccall "gsasl.h gsasl_md5" gsasl_md5 :: F.CString -> F.CSize -> F.Ptr F.CString -> IO F.CInt foreign import ccall "gsasl.h gsasl_sha1" gsasl_sha1 :: F.CString -> F.CSize -> F.Ptr F.CString -> IO F.CInt foreign import ccall "gsasl.h gsasl_hmac_md5" gsasl_hmac_md5 :: F.CString -> F.CSize -> F.CString -> F.CSize -> F.Ptr F.CString -> IO F.CInt foreign import ccall "gsasl.h gsasl_hmac_sha1" gsasl_hmac_sha1 :: F.CString -> F.CSize -> F.CString -> F.CSize -> F.Ptr F.CString -> IO F.CInt foreign import ccall "gsasl.h gsasl_nonce" gsasl_nonce :: F.CString -> F.CSize -> IO F.CInt foreign import ccall "gsasl.h gsasl_random" gsasl_random :: F.CString -> F.CSize -> IO F.CInt foreign import ccall "gsasl.h gsasl_free" gsasl_free :: F.Ptr a -> IO () -- }}}