gssapi (empty) → 0.1.0.0
raw patch · 9 files changed
+518/−0 lines, 9 filesdep +basedep +bytestringdep +resourcetsetup-changed
Dependencies added: base, bytestring, resourcet, transformers
Files
- LICENSE +30/−0
- README.md +72/−0
- Setup.hs +2/−0
- clib/hkrb5.c +22/−0
- clib/hkrb5.h +3/−0
- gssapi.cabal +30/−0
- src/Network/Security/GssApi.hs +221/−0
- src/Network/Security/GssTypes.hsc +22/−0
- src/Network/Security/Kerberos.hs +116/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2016, Ondrej Palkovsky++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Ondrej Palkovsky nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,72 @@+[](https://travis-ci.org/ondrap/gssapi) [](https://hackage.haskell.org/package/gssapi)++## GSSAPI and Kerberos bindings for Haskell++This library provides a simplified kerberos and GSSAPI bindings for the SPNEGO authentication.++- Modelled after [spnego-http-auth-nginx-module](https://github.com/stnoonan/spnego-http-auth-nginx-module)+- See [this](https://ping.force.com/Support/PingFederate/Integrations/How-to-configure-supported-browsers-for-Kerberos-NTLM) on how to configure browsers+- See [this](http://pythonhackers.com/p/bcandrea/spnego-http-auth-nginx-module) on how to+ configure keys on the windows AD++### Short story++#### On the AD side, you need to++- Create a new user, whose name should be the service name you'll be using Kerberos authentication on. E.g. `app.example`.+- Set the "User cannot change password" and "Password never expires" options on the account+- Set a strong password on it++From a Windows cmd.exe window, generate the service principals and keytabs for this user. You need an SPN named `host/foo.example.com`, and another named `HTTP/foo.example.com`. It is crucial that foo.example.com is the DNS name of your web site in the intranet, and it is an A record. Given that app.example is the account name you created, you would execute:++ C:\> ktpass -princ host/foo.example.com@EXAMPLE.COM -mapuser+ EXAMPLECOM\app.example -pass * -out host.keytab -ptype KRB5_NT_PRINCIPAL -crypto All++ C:\> ktpass -princ HTTP/foo.example.com@EXAMPLE.COM -mapuser+ EXAMPLECOM\app.example -pass * -out http.keytab -ptype KRB5_NT_PRINCIPAL -crypto All++Verify that the correct SPNs are created:++ C:\> setspn -Q */foo.example.com++it should yield both the `HTTP/` and `host/` SPNs, both mapped to the app.example user.++#### On the server side you need to++Create a krb5.keytab using ktutil, concatenating together the two SPNs keytabs:++ # ktutil+ ktutil: rkt host.keytab+ ktutil: rkt http.keytab+ ktutil: wkt /etc/krb5.keytab+ ktutil: quit+++Verify that the created keytab file has been built correctly:++ # klist -kt /etc/krb5.keytab+ Keytab name: WRFILE:/etc/krb5.keytab+ KVNO Timestamp Principal+ ---- ----------------- --------------------------------------------------------+ 9 02/19/13 04:02:48 HTTP/foo.example.com@EXAMPLE.COM+ 8 02/19/13 04:02:48 host/foo.example.com@EXAMPLE.COM++Key version numbers (KVNO) will be different in your case.++Verify that you are able to authenticate using the keytab, without password:++ # kinit -5 -V -k -t /etc/krb5.keytab HTTP/foo.example.com+ Authenticated to Kerberos v5++ # klist+ Ticket cache: FILE:/tmp/krb5cc_0+ Default principal: HTTP/foo.example.com@EXAMPLE.COM++ Valid starting Expires Service principal+ 02/19/13 17:37:42 02/20/13 03:37:40 krbtgt/EXAMPLE.COM@EXAMPLE.COM+ renew until 02/20/13 17:37:42++Make the keytab file accessible only by appropriate users or groups++ # chmod 440 /etc/krb5.keytab+ # chown root:nginx /etc/krb5.keytab
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ clib/hkrb5.c view
@@ -0,0 +1,22 @@+#include <krb5.h>+#include <string.h>++#include "krb5.h"+++/* Convenience function to simplify FFI to access krb5 login */+krb5_error_code _hkrb5_login(krb5_context kcontext, krb5_principal client, char *password)+{+ krb5_creds creds;+ krb5_get_init_creds_opt *gic_options = NULL;+ krb5_error_code code;++ memset(&creds, 0, sizeof(creds));+ krb5_get_init_creds_opt_alloc(kcontext, &gic_options);+ code = krb5_get_init_creds_password(kcontext, &creds, client, password, NULL, NULL, 0, NULL, gic_options);+ if (!code)+ krb5_free_cred_contents(kcontext, &creds);+ krb5_get_init_creds_opt_free(kcontext, gic_options);++ return code;+}
+ clib/hkrb5.h view
@@ -0,0 +1,3 @@+#include <krb5.h>++krb5_error_code _hkrb5_login(krb5_context kcontext, krb5_principal client, char *password);
+ gssapi.cabal view
@@ -0,0 +1,30 @@+name: gssapi+version: 0.1.0.0+synopsis: libgssapi and libkrb5 bindings for haskell+description: Simple bindings for libgssapi(SSO) and libkrb5(user/password) library.+license: BSD3+license-file: LICENSE+author: Ondrej Palkovsky+maintainer: palkovsky.ondrej@gmail.com+homepage: https://github.com/ondrap/gssapi+copyright: Ondrej Palkovsky+category: Network+build-type: Simple+extra-source-files: clib/hkrb5.h README.md+cabal-version: >=1.10++source-repository head+ type: git+ location: https://github.com/ondrap/gssapi.git++library+ exposed-modules: Network.Security.Kerberos, Network.Security.GssApi+ other-modules: Network.Security.GssTypes+ c-sources: clib/hkrb5.c+ includes: clib/hkrb5.h++ build-depends: base >=4.8 && <4.10, bytestring, resourcet, transformers+ default-language: Haskell2010+ ghc-options: -Wall -fwarn-incomplete-uni-patterns+ extra-libraries: gssapi_krb5, krb5+ hs-source-dirs: src
+ src/Network/Security/GssApi.hs view
@@ -0,0 +1,221 @@+{-# LANGUAGE CApiFFI #-}+{-# LANGUAGE EmptyDataDecls #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++-- | FFI binding to libgssapi+module Network.Security.GssApi (+ runGssCheck+ , GssException(..)+) where++import Control.Exception (Exception, bracketOnError,+ finally, mask_, throwIO)+import Control.Monad (void, when, (>=>))+import Control.Monad.IO.Class (MonadIO, liftIO)+import Control.Monad.Trans.Resource (ResourceT, allocate,+ runResourceT)+import Data.Bits ((.&.))+import qualified Data.ByteString.Char8 as BS+import Foreign (Ptr, Storable, alloca, nullPtr,+ peek, poke)+import Foreign.C.Types+import Foreign.Marshal.Alloc (free, malloc)++import Network.Security.GssTypes++-- | Exception that can be thrown by functions from this module+data GssException = GssException Word BS.ByteString+ deriving (Show)+instance Exception GssException++data {-# CTYPE "const struct gss_OID_desc_struct" #-} OidDescStruct+newtype {-# CTYPE "gss_OID_desc" #-} GssOID = GssOID (Ptr OidDescStruct)+foreign import capi "gssapi/gssapi_krb5.h value GSS_KRB5_NT_PRINCIPAL_NAME" gssKrb5NtPrincipalName :: GssOID+foreign import capi "gssapi/gssapi_krb5.h value GSS_C_NO_OID" gssCNoOid :: GssOID++newtype {-# CTYPE "gss_name_t" #-} GssNameT = GssNameT (Ptr ()) deriving (Storable)+foreign import capi "gssapi/gssapi.h value GSS_C_NO_NAME" gssCNoName :: GssNameT++newtype {-# CTYPE "gss_cred_id_t" #-} GssCredIdT = GssCredIdT (Ptr ()) deriving (Storable)+foreign import capi "gssapi/gssapi.h value GSS_C_NO_CREDENTIAL" gssCNoCredential :: GssCredIdT++newtype {-# CTYPE "gss_oid_set" #-} GssOIDSet = GssOIDSet (Ptr ()) deriving (Storable)+foreign import capi "gssapi/gssapi.h value GSS_C_NO_OID_SET" gssCNoOidSet :: GssOIDSet++foreign import capi "gssapi/gssapi.h value GSS_C_INDEFINITE" gscCIndefinite :: CUInt++newtype {-# CTYPE "gss_cred_usage_t" #-} GssCredUsageT = GssCredUsageT CInt deriving (Storable)+foreign import capi "gssapi/gssapi.h value GSS_C_ACCEPT" gssCAccept :: GssCredUsageT++newtype {-# CTYPE "gss_ctx_id_t" #-} GssCtxIdT = GssCtxIdT (Ptr ()) deriving (Storable)+foreign import capi "gssapi/gssapi.h value GSS_C_NO_CONTEXT" gssCNoContext :: GssCtxIdT++newtype {-# CTYPE "gss_channel_bindings_t" #-} GssChannelBindingsT = GssChannelBindingsT (Ptr ()) deriving (Storable)+foreign import capi "gssapi/gssapi.h value GSS_C_NO_CHANNEL_BINDINGS" gssCNoChannelBindings :: GssChannelBindingsT++foreign import capi "gssapi/gssapi.h value GSS_C_NO_BUFFER" gssCNoBuffer :: Ptr BufferDesc++foreign import capi "gssapi/gssapi.h value GSS_C_MECH_CODE" gssCMechCode :: CUInt++foreign import capi "gssapi/gssapi.h GSS_ERROR" _gssError :: CUInt -> CUInt+foreign import capi "gssapi/gssapi.h value GSS_S_CONTINUE_NEEDED" _gssSContinueNeeded :: CUInt++gssError :: CUInt -> Bool+gssError major = _gssError major /= 0++gssContinueNeeded :: CUInt -> Bool+gssContinueNeeded status = status .&. _gssSContinueNeeded /= 0++foreign import ccall unsafe "gssapi/gssapi.h gss_import_name"+ _gss_import_name :: Ptr CUInt -> Ptr BufferDesc -> GssOID -> Ptr GssNameT -> IO CUInt++foreign import ccall unsafe "gssapi/gssapi.h gss_release_name"+ _gss_release_name :: Ptr CUInt -> GssNameT -> IO CUInt++gssReleaseName :: GssNameT -> IO ()+gssReleaseName name =+ alloca $ \minor ->+ void $ _gss_release_name minor name++gssImportName :: BS.ByteString -> ResourceT IO GssNameT+gssImportName svc = snd <$> allocate doimport gssReleaseName+ where+ doimport =+ withBufferDesc svc $ \bptr ->+ alloca $ \minor ->+ alloca $ \gssname -> do+ major <- _gss_import_name minor bptr gssKrb5NtPrincipalName gssname+ whenGssOk major minor $ peek gssname++foreign import ccall unsafe "gssapi/gssapi.h gss_display_name"+ _gss_display_name :: Ptr CUInt -> GssNameT -> Ptr BufferDesc -> Ptr (Ptr GssOID) -> IO CUInt++foreign import ccall unsafe "gssapi/gssapi.h gss_release_buffer"+ _gss_release_buffer :: Ptr CUInt -> Ptr BufferDesc -> IO CUInt++peekBuffer :: Ptr BufferDesc -> IO BS.ByteString+peekBuffer bdesc = do+ (BufferDesc len ptr) <- peek bdesc+ BS.packCStringLen (ptr, len)++---+withBufferDesc :: BS.ByteString -> (Ptr BufferDesc -> IO a) -> IO a+withBufferDesc "" code =+ alloca $ \bdesc -> do+ poke bdesc (BufferDesc 0 nullPtr)+ code bdesc `finally` gssReleaseBuffer bdesc+withBufferDesc str code =+ BS.useAsCStringLen str $ \(cstr, len) ->+ alloca $ \bdesc -> do+ poke bdesc (BufferDesc len cstr)+ code bdesc++gssDisplayName :: MonadIO m => GssNameT -> m BS.ByteString+gssDisplayName gname =+ liftIO $ mask_ $+ withBufferDesc "" $ \bdesc ->+ alloca $ \minor -> do+ poke bdesc (BufferDesc 0 nullPtr)+ major <- _gss_display_name minor gname bdesc nullPtr+ whenGssOk major minor $ peekBuffer bdesc++foreign import ccall unsafe "gssapi/gssapi.h gss_display_status"+ _gss_display_status :: Ptr CUInt -> CUInt -> CUInt -> GssOID -> Ptr CUInt -> Ptr BufferDesc -> IO CUInt++whenGssOk :: CUInt -> Ptr CUInt -> IO a -> IO a+whenGssOk major minor code+ | gssError major = peek minor >>= throwGssException+ | otherwise = code+ where+ throwGssException status = do+ errtxt <- gssDisplayStatus status+ throwIO $ GssException (fromIntegral status) errtxt++gssDisplayStatus :: CUInt -> IO BS.ByteString+gssDisplayStatus rstatus =+ alloca $ \minor ->+ alloca $ \msgctx -> do+ poke msgctx 0+ withBufferDesc "" $ \bdesc -> do+ -- TODO: This could produce more than 1 line, we should fetch all of them+ poke bdesc (BufferDesc 0 nullPtr)+ major <- _gss_display_status minor rstatus gssCMechCode gssCNoOid msgctx bdesc+ whenGssOk major minor $ peekBuffer bdesc++foreign import ccall safe "gssapi/gssapi.h gss_acquire_cred"+ _gss_acquire_cred :: Ptr CUInt -> GssNameT -> CUInt -> GssOIDSet -> GssCredUsageT -> Ptr GssCredIdT -> Ptr GssOIDSet -> Ptr CUInt -> IO CUInt++gssAcquireCred :: GssNameT -> ResourceT IO GssCredIdT+gssAcquireCred name =+ snd <$> allocate doalloc gssReleaseCred+ where+ doalloc =+ liftIO $ alloca $ \minor ->+ alloca $ \credid -> do+ major <- _gss_acquire_cred minor name gscCIndefinite gssCNoOidSet gssCAccept credid nullPtr nullPtr+ whenGssOk major minor $ peek credid++foreign import ccall unsafe "gssapi/gssapi.h gss_release_cred"+ _gss_release_cred :: Ptr CUInt -> GssCredIdT -> IO CUInt++gssReleaseCred :: GssCredIdT -> IO ()+gssReleaseCred name = alloca $ \minor -> void $ _gss_release_cred minor name++gssReleaseBuffer :: Ptr BufferDesc -> IO ()+gssReleaseBuffer bdesc = void $ alloca $ \minor -> _gss_release_buffer minor bdesc++foreign import ccall safe "gssapi/gssapi.h gss_accept_sec_context"+ _gss_accept_sec_context :: Ptr CUInt -> Ptr GssCtxIdT -> GssCredIdT -> Ptr BufferDesc -> GssChannelBindingsT+ -> Ptr GssNameT -> Ptr GssOID -> Ptr BufferDesc -> Ptr CUInt -> Ptr CUInt+ -> Ptr GssCredIdT -> IO CUInt++foreign import ccall unsafe "gssapi/gssapi.h gss_delete_sec_context"+ _gss_delete_sec_context :: Ptr CUInt -> Ptr GssCtxIdT -> Ptr BufferDesc -> IO CUInt++data SecContextResult = SecContextResult {+ sContext :: Ptr GssCtxIdT+ , sClientName :: BS.ByteString+ , sOutputToken :: BS.ByteString+}++gssAcceptSecContext :: GssCredIdT -> BS.ByteString -> ResourceT IO SecContextResult+gssAcceptSecContext myCreds input = snd <$> allocate runAccept freeResult+ where+ freeResult SecContextResult{sContext} = do+ void $ alloca $ \minor -> _gss_delete_sec_context minor sContext gssCNoBuffer+ free sContext+ runAccept =+ alloca $ \minor ->+ bracketOnError malloc free $ \sContext -> do+ poke sContext gssCNoContext+ alloca $ \bclient -> do+ poke bclient gssCNoName+ withBufferDesc input $ \binput ->+ withBufferDesc "" $ \boutput -> do+ major <- _gss_accept_sec_context minor sContext myCreds binput+ gssCNoChannelBindings bclient nullPtr+ boutput nullPtr nullPtr nullPtr+ whenGssOk major minor $ do+ clname <- peek bclient+ when (gssContinueNeeded major) $ do+ void $ _gss_delete_sec_context minor sContext gssCNoBuffer+ gssReleaseName clname+ throwIO (GssException 0 "Only one auth interaction allowed.")+ sClientName <- gssDisplayName clname `finally` gssReleaseName clname+ sOutputToken <- peekBuffer boutput+ return SecContextResult{..}++-- | Perform a simple gss_accept_sec_context test. Throws exception upon any error.+runGssCheck ::+ Maybe BS.ByteString -- ^ Service principal (e.g. "HTTP/foo.example.com@EXAMPLE.COM")+ -> BS.ByteString -- ^ Input token (content of the Authorization: Negotiate header)+ -> IO (BS.ByteString, BS.ByteString) -- ^ (client name, output token)+runGssCheck mcredname input_token =+ runResourceT $ do+ cred <- maybe (return gssCNoCredential) (gssImportName >=> gssAcquireCred) mcredname+ ctx <- gssAcceptSecContext cred input_token+ return (sClientName ctx, sOutputToken ctx)
+ src/Network/Security/GssTypes.hsc view
@@ -0,0 +1,22 @@+{-# OPTIONS_HADDOCK hide #-}+{-# LANGUAGE CPP #-}++-- | Implementation of FFI to gss_buffer_desc structure+module Network.Security.GssTypes where++import Foreign+import Foreign.C.String++#include <gssapi/gssapi.h>+#include <gssapi/gssapi_krb5.h>++data BufferDesc = BufferDesc Int CString++instance Storable BufferDesc where+ sizeOf _ = #{size gss_buffer_desc}+ alignment _ = alignment (undefined :: Ptr ())+ poke p (BufferDesc len val) = do+ #{poke gss_buffer_desc, length} p len+ #{poke gss_buffer_desc, value} p val+ peek p = BufferDesc <$> #{peek gss_buffer_desc, length} p+ <*> #{peek gss_buffer_desc, value} p
+ src/Network/Security/Kerberos.hs view
@@ -0,0 +1,116 @@+{-# LANGUAGE EmptyDataDecls #-}+{-# LANGUAGE ForeignFunctionInterface #-}+{-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE OverloadedStrings #-}++-- | FFI binding to libkrb5 library++module Network.Security.Kerberos (+ krb5Login+ , krb5Resolve+ , KrbException(..)+) where++import Control.Exception (Exception, bracket, mask_, throwIO)+import Control.Monad (when)+import qualified Data.ByteString.Char8 as BS+import Foreign+import Foreign.C.String+import Foreign.C.Types++-- | Exception raised by this module+data KrbException = KrbException Word BS.ByteString+ deriving (Show)+instance Exception KrbException++data KerberosCTX+data KerberosPrincipal++type Context = Ptr KerberosCTX+type Principal = ForeignPtr KerberosPrincipal++foreign import ccall unsafe "krb5.h krb5_init_context"+ _krb5_init_context :: Ptr (Ptr KerberosCTX) -> IO CInt++foreign import ccall unsafe "krb5.h krb5_free_context"+ _krb5_free_context :: Ptr KerberosCTX -> IO ()++krb5_init_context :: IO Context+krb5_init_context =+ alloca $ \nptr -> do+ code <- _krb5_init_context nptr+ if | code == 0 -> peek nptr+ | otherwise -> throwIO (KrbException (fromIntegral code) "Cannot initialize kerberos context.")++withKrbContext :: (Context -> IO a) -> IO a+withKrbContext = bracket krb5_init_context _krb5_free_context++foreign import ccall unsafe "krb5.h krb5_parse_name"+ _krb5_parse_name :: Ptr KerberosCTX -> CString -> Ptr (Ptr KerberosPrincipal) -> IO CInt++foreign import ccall unsafe "krb5.h &krb5_free_principal"+ _krb5_free_principal :: FinalizerEnvPtr KerberosCTX KerberosPrincipal++krb5_parse_name :: Context -> BS.ByteString -> IO Principal+krb5_parse_name ctx name =+ alloca $ \nprincipal ->+ BS.useAsCString name $ \cname ->+ mask_ $ do+ code <- _krb5_parse_name ctx cname nprincipal+ if | code == 0 -> do+ ptr <- peek nprincipal+ newForeignPtrEnv _krb5_free_principal ctx ptr+ | otherwise -> krb5_throw ctx code++foreign import ccall unsafe "krb5.h krb5_unparse_name"+ _krb5_unparse_name :: Context -> Ptr KerberosPrincipal -> Ptr CString -> IO CInt++krb5_unparse_name :: Context -> Principal -> IO BS.ByteString+krb5_unparse_name ctx principal =+ withForeignPtr principal $ \ptrprincipal ->+ alloca $ \nstring ->+ mask_ $ do+ code <- _krb5_unparse_name ctx ptrprincipal nstring+ if | code == 0 -> do+ ctxt <- peek nstring+ result <- BS.packCString ctxt+ free ctxt+ return result+ | otherwise -> krb5_throw ctx code++foreign import ccall unsafe "krb5.h krb5_get_error_message"+ _krb5_get_error_message :: Ptr KerberosCTX -> CInt -> IO CString+foreign import ccall unsafe "krb5.h krb5_free_error_message"+ _krb5_free_error_message :: Ptr KerberosCTX -> CString -> IO ()++krb5_throw :: Context -> CInt -> IO a+krb5_throw ctx code = do+ errtext <- bracket+ (_krb5_get_error_message ctx code)+ (_krb5_free_error_message ctx)+ BS.packCString+ throwIO (KrbException (fromIntegral code) errtext)++foreign import ccall safe "hkrb5.h _hkrb5_login"+ _krb5_login :: Ptr KerberosCTX -> Ptr KerberosPrincipal -> CString -> IO CInt++krb5_login :: Context -> Principal -> BS.ByteString -> IO ()+krb5_login ctx principal password = do+ code <- withForeignPtr principal $ \ptrprincipal ->+ BS.useAsCString password $ \cpass ->+ _krb5_login ctx ptrprincipal cpass+ when (code /= 0) $ krb5_throw ctx code++-- | Try to login with principal and password. If logging fails, exception is raised+krb5Login :: BS.ByteString -> BS.ByteString -> IO ()+krb5Login svcname password =+ withKrbContext $ \ctx -> do+ principal <- krb5_parse_name ctx svcname+ krb5_login ctx principal password++-- | Call 'krb5_unparse . krb5_parse' - i.e. add system-wide default realm to the principal name+krb5Resolve :: BS.ByteString -> IO BS.ByteString+krb5Resolve svcname =+ withKrbContext $ \ctx -> do+ principal <- krb5_parse_name ctx svcname+ krb5_unparse_name ctx principal