diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -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.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -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
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/clib/hkrb5.c b/clib/hkrb5.c
new file mode 100644
--- /dev/null
+++ b/clib/hkrb5.c
@@ -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;
+}
diff --git a/clib/hkrb5.h b/clib/hkrb5.h
new file mode 100644
--- /dev/null
+++ b/clib/hkrb5.h
@@ -0,0 +1,3 @@
+#include <krb5.h>
+
+krb5_error_code _hkrb5_login(krb5_context kcontext, krb5_principal client, char *password);
diff --git a/gssapi.cabal b/gssapi.cabal
new file mode 100644
--- /dev/null
+++ b/gssapi.cabal
@@ -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
diff --git a/src/Network/Security/GssApi.hs b/src/Network/Security/GssApi.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Security/GssApi.hs
@@ -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)
diff --git a/src/Network/Security/GssTypes.hsc b/src/Network/Security/GssTypes.hsc
new file mode 100644
--- /dev/null
+++ b/src/Network/Security/GssTypes.hsc
@@ -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
diff --git a/src/Network/Security/Kerberos.hs b/src/Network/Security/Kerberos.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Security/Kerberos.hs
@@ -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
