packages feed

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 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 @@+[![Build Status](https://travis-ci.org/ondrap/gssapi.svg?branch=master)](https://travis-ci.org/ondrap/gssapi) [![Hackage](https://img.shields.io/hackage/v/gssapi.svg)](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