libssh2-0.2.0.9: src/Network/SSH/Client/LibSSH2/Foreign.chs
{-# LANGUAGE CPP, ForeignFunctionInterface #-}
#ifdef __APPLE__
#define _ANSI_SOURCE
#define __OSX_AVAILABLE_STARTING(_mac, _iphone)
#define __OSX_AVAILABLE_BUT_DEPRECATED(_macIntro, _macDep, _iphoneIntro, _iphoneDep)
#endif
#include "libssh2_local.h"
#include <libssh2.h>
#include <libssh2_sftp.h>
{# context lib="ssh2" prefix="libssh2" #}
module Network.SSH.Client.LibSSH2.Foreign
(-- * Types
KnownHosts, KnownHostResult (..), KnownHostType (..), KnownHost (..),
-- * Session functions
initialize, exit,
initSession, freeSession, disconnectSession,
keepaliveConfig,
handshake,
setBlocking,
-- * Known hosts functions
initKnownHosts, freeKnownHosts, knownHostsReadFile,
getHostKey, checkKnownHost,
-- * Authentication
publicKeyAuthFile,
usernamePasswordAuth,
-- * Channel functions
openChannelSession, closeChannel, freeChannel,
channelSendEOF, channelWaitEOF, channelIsEOF,
readChannel, readChannelStderr, writeChannel,
writeChannelFromHandle, readChannelToHandle,
channelProcess, channelExecute, channelShell,
requestPTY, requestPTYEx,
directTcpIpEx,
channelExitStatus, channelExitSignal,
scpSendChannel, scpReceiveChannel, pollChannelRead,
-- * SFTP functions
sftpInit, sftpShutdown,
sftpOpenDir, sftpReadDir, sftpCloseHandle,
sftpOpenFile,
sftpRenameFile, sftpRenameFileEx,
sftpWriteFileFromHandler, sftpWriteFileFromBytes,
sftpReadFileToHandler, sftpFstat, sftpDeleteFile,
RenameFlag (..), SftpFileTransferFlags (..),
SftpAttributes (..),
-- * SSH Agent functions
Agent (..), AgentPublicKey,
agentInit,
agentConnect, agentDisconnect,
agentListIdentities,
agentGetIdentity,
agentGetIdentities,
agentFree,
agentPublicKeyComment,
agentPublicKeyBlob,
agentUserAuth,
agentAuthenticate,
-- * Debug
TraceFlag (..), setTraceMode
) where
import Control.Exception (throw, tryJust)
import Control.Monad (void)
import Data.Time.Clock.POSIX
import Foreign hiding (void)
import Foreign.C.Types
import Foreign.C.String
import System.IO
#if MIN_VERSION_network(3,0,0)
import Network.Socket (Socket, withFdSocket)
#else
import Network.Socket (Socket(MkSocket), isReadable)
#endif
import qualified Data.ByteString as BSS
import qualified Data.ByteString.Unsafe as BSS
import Network.SSH.Client.LibSSH2.Types
import Network.SSH.Client.LibSSH2.Errors
#ifdef GCRYPT
import Network.SSH.Client.LibSSH2.GCrypt
#endif
-- Known host flags. See libssh2 documentation.
data KnownHostType =
TYPE_MASK
| TYPE_PLAIN
| TYPE_SHA1
| TYPE_CUSTOM
| KEYENC_MASK
| KEYENC_RAW
| KEYENC_BASE64
| KEY_MASK
| KEY_SHIFT
| KEY_RSA1
| KEY_SSHRSA
| KEY_SSHDSS
| KEY_ECDSA_256
| KEY_ECDSA_384
| KEY_ECDSA_521
| KEY_ED25519
| KEY_UNKNOWN
deriving (Eq, Show)
kht2int :: KnownHostType -> CInt
kht2int TYPE_MASK = 0xffff
kht2int TYPE_PLAIN = 1
kht2int TYPE_SHA1 = 2
kht2int TYPE_CUSTOM = 3
kht2int KEYENC_MASK = 3 `shiftL` 16
kht2int KEYENC_RAW = 1 `shiftL` 16
kht2int KEYENC_BASE64 = 2 `shiftL` 16
kht2int KEY_MASK = 15 `shiftL` 18
kht2int KEY_SHIFT = 18
kht2int KEY_RSA1 = 1 `shiftL` 18
kht2int KEY_SSHRSA = 2 `shiftL` 18
kht2int KEY_SSHDSS = 3 `shiftL` 18
kht2int KEY_ECDSA_256 = 4 `shiftL` 18
kht2int KEY_ECDSA_384 = 5 `shiftL` 18
kht2int KEY_ECDSA_521 = 6 `shiftL` 18
kht2int KEY_ED25519 = 7 `shiftL` 18
kht2int KEY_UNKNOWN = 15 `shiftL` 18
int2kht :: CInt -> KnownHostType
int2kht 0xffff = TYPE_MASK
int2kht 1 = TYPE_PLAIN
int2kht 2 = TYPE_SHA1
int2kht 3 = TYPE_CUSTOM
int2kht 18 = KEY_SHIFT
int2kht i
| i == 3 `shiftL` 16 = KEYENC_MASK
| i == 1 `shiftL` 16 = KEYENC_RAW
| i == 2 `shiftL` 16 = KEYENC_BASE64
| i == 15 `shiftL` 18 = KEY_MASK
| i == 1 `shiftL` 18 = KEY_RSA1
| i == 2 `shiftL` 18 = KEY_SSHRSA
| i == 3 `shiftL` 18 = KEY_SSHDSS
| i == 4 `shiftL` 18 = KEY_ECDSA_256
| i == 5 `shiftL` 18 = KEY_ECDSA_384
| i == 6 `shiftL` 18 = KEY_ECDSA_521
| i == 7 `shiftL` 18 = KEY_ED25519
| i == 15 `shiftL` 18 = KEY_UNKNOWN
| otherwise = error $ "Unsupported known host type: " ++ show i
typemask2int :: [KnownHostType] -> CInt
typemask2int list = foldr (.|.) 0 (map kht2int list)
-- | Host key types. See libssh2 documentation.
data HostKeyType =
UNKNOWN
| RSA
| DSS
| ECDSA_256
| ECDSA_384
| ECDSA_521
| ED25519
deriving (Enum, Eq, Ord)
instance Show HostKeyType where
show UNKNOWN = "unknown"
show RSA = "ssh-rsa"
show DSS = "ssh-dss"
show ECDSA_256 = "ecdsa-sha2-nistp256"
show ECDSA_384 = "ecdsa-sha2-nistp384"
show ECDSA_521 = "ecdsa-sha2-nistp521"
show ED25519 = "ssh-ed25519"
int2hkt :: Integral n => n -> HostKeyType
int2hkt = toEnum . fromIntegral
-- Result of matching host against known_hosts.
data KnownHostResult =
MATCH
| MISMATCH
| NOTFOUND
| FAILURE
deriving (Eq, Show, Ord, Enum)
int2khresult :: CInt -> KnownHostResult
int2khresult = toEnum . fromIntegral
data KnownHost = KnownHost {
khMagic :: CUInt,
khNode :: Ptr (),
khName :: String,
khKey :: String,
khTypeMask :: [KnownHostType] }
deriving (Eq, Show)
init_crypto :: Bool -> CInt
init_crypto False = 1
init_crypto True = 0
#if MIN_VERSION_network(3,0,0)
ssh2socket :: Socket
#ifdef mingw32_HOST_OS
#ifdef x86_64_HOST_ARCH
-> CULLong
#else
-> IO CUInt
#endif
#else
-> IO CInt
#endif
ssh2socket s =
#ifdef mingw32_HOST_OS
fromIntegral <$> withFdSocket s pure
#else
withFdSocket s pure
#endif
#else
ssh2socket :: Socket
#ifdef mingw32_HOST_OS
#ifdef x86_64_HOST_ARCH
-> CULLong
#else
-> CUInt
#endif
#else
-> CInt
#endif /* mingw32_HOST_OS */
ssh2socket (MkSocket s _ _ _ _) =
#ifdef mingw32_HOST_OS
(fromIntegral s)
#else
s
#endif
#endif /* MIN_VERSION_network(3,0,0) */
{# fun init as initialize_
{ init_crypto `Bool' } -> `Int' #}
-- | Initialize libssh2. Pass True to enable encryption
-- or False to disable it.
initialize :: Bool -> IO ()
#ifdef GCRYPT
initialize flags = void . handleInt (Nothing :: Maybe Session) $ gcryptFix >> initialize_ flags
#else
initialize flags = void . handleInt (Nothing :: Maybe Session) $ initialize_ flags
#endif
-- | Deinitialize libssh2.
#ifdef mingw32_HOST_OS
foreign import ccall safe "libssh2_exit"
exit:: IO ()
#else
{# fun exit as exit { } -> `()' #}
#endif
-- | Create Session object
initSession :: IO Session
initSession = handleNullPtr (Nothing :: Maybe Session) sessionFromPointer $
{# call session_init_ex #} nullFunPtr nullFunPtr nullFunPtr nullPtr
{# fun session_free as freeSession_
{ toPointer `Session' } -> `Int' #}
-- | Free Session object's memory
freeSession :: Session -> IO ()
freeSession session = void . handleInt (Just session) $ freeSession_ session
{# fun session_disconnect_ex as disconnectSessionEx
{ toPointer `Session', `Int', `String', `String' } -> `Int' #}
-- | Disconnect session (but do not free memory)
disconnectSession :: Session
-> String -- ^ Goodbye message
-> IO ()
disconnectSession s msg = void . handleInt (Just s) $ disconnectSessionEx s 11 msg ""
{# fun keepalive_config as keepaliveConfig
{ toPointer `Session', bool2int `Bool', `Int' } -> `()' #}
{# fun session_set_blocking as setBlocking
{ toPointer `Session', bool2int `Bool' } -> `()' #}
bool2int :: Bool -> CInt
bool2int True = 1
bool2int False = 0
#if MIN_VERSION_network(3,0,0)
{# fun session_handshake
{ `Ptr ()', `CInt' } -> `Int' #}
handshake_ :: Session -> Socket -> IO Int
handshake_ session socket = do
session_handshake (toPointer session) =<< ssh2socket socket
#else
{# fun session_handshake as handshake_
{ toPointer `Session', ssh2socket `Socket' } -> `Int' #}
#endif
-- | Run SSH handshake on network socket.
handshake :: Session -> Socket -> IO ()
handshake session socket = do
sessionSetSocket session (Just socket)
void $ handleInt (Just session)
$ handshake_ session socket
{# fun knownhost_init as initKnownHosts_
{ toPointer `Session' } -> `Ptr ()' id #}
-- | Create KnownHosts object for given session.
initKnownHosts :: Session -> IO KnownHosts
initKnownHosts session = handleNullPtr (Nothing :: Maybe Session) knownHostsFromPointer $ initKnownHosts_ session
-- | Free KnownHosts object's memory
{# fun knownhost_free as freeKnownHosts
{ toPointer `KnownHosts' } -> `()' #}
{# fun knownhost_readfile as knownHostsReadFile_
{ toPointer `KnownHosts', `String', id `CInt' } -> `Int' #}
-- | Read known hosts from file
knownHostsReadFile :: KnownHosts
-> FilePath -- ^ Path to known_hosts file
-> IO Int
knownHostsReadFile kh path = handleInt (Nothing :: Maybe Session) $ knownHostsReadFile_ kh path 1
{# fun session_hostkey as getHostKey_
{ toPointer `Session', alloca- `Size' peek*, alloca- `CInt' peek* } -> `Ptr CChar' id #}
-- | Get remote host public key and its type
getHostKey :: Session -> IO (BSS.ByteString, HostKeyType)
getHostKey session = do
(keyPtr, keySize, keyType) <- getHostKey_ session
key <- BSS.packCStringLen (keyPtr, fromIntegral keySize)
pure (key, int2hkt keyType)
{# fun knownhost_checkp as checkKnownHost_
{ toPointer `KnownHosts',
`String',
`Int',
id `Ptr CChar',
`Int',
typemask2int `[KnownHostType]',
castPtr `Ptr ()' } -> `KnownHostResult' int2khresult #}
-- | Check host data against known hosts.
checkKnownHost :: KnownHosts --
-> String -- ^ Host name
-> Int -- ^ Port number (usually 22)
-> BSS.ByteString -- ^ Host public key
-> [KnownHostType] -- ^ Host flags (see libssh2 documentation)
-> IO KnownHostResult
checkKnownHost kh host port key flags = BSS.useAsCStringLen key $ \(keyPtr, keySize) -> do
checkKnownHost_ kh host port keyPtr keySize flags nullPtr
-- TODO: I don't see the '&' in the libssh2 docs?
{# fun userauth_publickey_fromfile_ex as publicKeyAuthFile_
{ toPointer `Session',
`String' &,
`String',
`String',
`String' } -> `Int' #}
-- | Perform public key authentication.
publicKeyAuthFile :: Session -- ^ Session
-> String -- ^ Username
-> String -- ^ Path to public key
-> String -- ^ Path to private key
-> String -- ^ Passphrase
-> IO ()
publicKeyAuthFile session username public private passphrase = void . handleInt (Just session) $
publicKeyAuthFile_ session username public private passphrase
-- | Perform username/password authentication.
usernamePasswordAuth :: Session -- ^ Session
-> String -- ^ Username
-> String -- ^ Password
-> IO ()
usernamePasswordAuth session username password =
withCString username $ \usernameptr -> do
withCString password $ \passwordptr -> do
void . handleInt (Just session) $
{# call userauth_password_ex #} (toPointer session) usernameptr (toEnum $ length username) passwordptr (toEnum $ length password) nullFunPtr
{# fun channel_open_ex as openSessionChannelEx
{ toPointer `Session',
`String' &,
`Int', `Int',
`String' & } -> `Ptr ()' id #}
{# fun channel_direct_tcpip_ex as directTcpIpEx_
{ toPointer `Session',
`String',
`Int',
`String',
`Int' } -> `Ptr ()' id #}
directTcpIpEx :: Session -> String -> Int -> String -> Int -> IO Channel
directTcpIpEx s host port shost sport = handleNullPtr (Just s) (channelFromPointer s) $ directTcpIpEx_ s host port shost sport
-- | Open a channel for session.
openChannelSession :: Session -> IO Channel
openChannelSession s = handleNullPtr (Just s) (channelFromPointer s) $
openSessionChannelEx s "session" 65536 32768 ""
channelProcess :: Channel -> String -> String -> IO ()
channelProcess ch kind command = void . handleInt (Just $ channelSession ch) $
channelProcessStartup_ ch kind command
-- | Execute command
channelExecute :: Channel -> String -> IO ()
channelExecute c command = channelProcess c "exec" command
{# fun channel_process_startup as channelProcessStartup_
{ toPointer `Channel',
`String' &,
`String' & } -> `Int' #}
-- | Execute shell command
channelShell :: Channel -> IO ()
channelShell c = void . handleInt (Just $ channelSession c) $ do
withCStringLen "shell" $ \(s,l) -> do
res <- channelProcessStartup_'_ (toPointer c) s (fromIntegral l) nullPtr 0
return $ (res :: CInt)
{# fun channel_request_pty_ex as requestPTYEx
{ toPointer `Channel',
`String' &,
`String' &,
`Int', `Int',
`Int', `Int' } -> `Int' #}
requestPTY :: Channel -> String -> IO ()
requestPTY ch term = void . handleInt (Just $ channelSession ch) $ requestPTYEx ch term "" 0 0 0 0
readChannelEx :: Channel -> Int -> Size -> IO BSS.ByteString
readChannelEx ch i size = do
allocaBytes (fromIntegral size) $ \buffer -> do
rc <- handleInt (Just $ channelSession ch) $ {# call channel_read_ex #} (toPointer ch) (fromIntegral i) buffer size
BSS.packCStringLen (buffer, fromIntegral rc)
-- | Read data from channel.
readChannel :: Channel --
-> Size -- ^ Amount of data to read
-> IO BSS.ByteString
readChannel c sz = readChannelEx c 0 sz
-- | Read data from channel.
readChannelStderr :: Channel --
-> Size -- ^ Amount of data to read
-> IO BSS.ByteString
readChannelStderr c sz = readChannelEx c {#const SSH_EXTENDED_DATA_STDERR#} sz
-- | Write data to channel.
writeChannel :: Channel -> BSS.ByteString -> IO ()
writeChannel ch bs =
BSS.unsafeUseAsCString bs $ go 0 (fromIntegral $ BSS.length bs)
where
go :: Int -> CULong -> CString -> IO ()
go offset len cstr = do
written <- handleInt (Just $ channelSession ch)
$ {# call channel_write_ex #} (toPointer ch)
0
(cstr `plusPtr` offset)
(fromIntegral len)
if fromIntegral written < len
then go (offset + fromIntegral written) (len - fromIntegral written) cstr
else return ()
{# fun channel_send_eof as channelSendEOF_
{ toPointer `Channel' } -> `Int' #}
channelSendEOF :: Channel -> IO ()
channelSendEOF channel = void . handleInt (Just $ channelSession channel) $ channelSendEOF_ channel
{# fun channel_wait_eof as channelWaitEOF_
{ toPointer `Channel' } -> `Int' #}
channelWaitEOF :: Channel -> IO ()
channelWaitEOF channel = void . handleInt (Just $ channelSession channel) $ channelWaitEOF_ channel
data TraceFlag =
T_TRANS
| T_KEX
| T_AUTH
| T_CONN
| T_SCP
| T_SFTP
| T_ERROR
| T_PUBLICKEY
| T_SOCKET
deriving (Eq, Show)
tf2int :: TraceFlag -> CInt
tf2int T_TRANS = 1 `shiftL` 1
tf2int T_KEX = 1 `shiftL` 2
tf2int T_AUTH = 1 `shiftL` 3
tf2int T_CONN = 1 `shiftL` 4
tf2int T_SCP = 1 `shiftL` 5
tf2int T_SFTP = 1 `shiftL` 6
tf2int T_ERROR = 1 `shiftL` 7
tf2int T_PUBLICKEY = 1 `shiftL` 8
tf2int T_SOCKET = 1 `shiftL` 9
trace2int :: [TraceFlag] -> CInt
trace2int flags = foldr (.|.) 0 (map tf2int flags)
{# fun trace as setTraceMode
{ toPointer `Session', trace2int `[TraceFlag]' } -> `()' #}
-- | Write all data to channel from handle.
-- Returns amount of transferred data.
writeChannelFromHandle :: Channel -> Handle -> IO Integer
writeChannelFromHandle ch h =
let
go :: Integer -> Ptr a -> IO Integer
go done buffer = do
sz <- hGetBuf h buffer bufferSize
send 0 (fromIntegral sz) buffer
let newDone = done + fromIntegral sz
if sz < bufferSize
then return newDone
else go newDone buffer
send :: Int -> CLong -> Ptr a -> IO ()
send _ 0 _ = return ()
send written size buffer = do
sent <- handleInt (Just $ channelSession ch) $
{# call channel_write_ex #}
(toPointer ch)
0
(plusPtr buffer written)
(fromIntegral size)
send (written + fromIntegral sent) (size - fromIntegral sent) buffer
bufferSize = 0x100000
in allocaBytes bufferSize $ go 0
-- | Read all data from channel to handle.
-- Returns amount of transferred data.
readChannelToHandle :: Channel -> Handle -> Offset -> IO Integer
readChannelToHandle ch h fileSize = do
allocaBytes bufferSize $ \buffer ->
readChannelCB ch buffer bufferSize fileSize callback
where
callback buffer size = hPutBuf h buffer size
bufferSize :: Int
bufferSize = 0x100000
readChannelCB :: Channel -> CString -> Int -> Offset -> (CString -> Int -> IO ()) -> IO Integer
readChannelCB ch buffer bufferSize fileSize callback =
let go got = do
let toRead = min (fromIntegral fileSize - got) (fromIntegral bufferSize)
sz <- handleInt (Just $ channelSession ch) $
{# call channel_read_ex #}
(toPointer ch)
0
buffer
(fromIntegral toRead)
let isz :: Integer
isz = fromIntegral sz
callback buffer (fromIntegral sz)
eof <- {# call channel_eof #} (toPointer ch)
let newGot = got + fromIntegral sz
if (eof == 1) || (newGot == fromIntegral fileSize)
then do
return isz
else do
rest <- go newGot
return $ isz + rest
in go (0 :: Integer)
{# fun channel_eof as channelIsEOF
{ toPointer `Channel' } -> `Bool' handleBool* #}
{# fun channel_close as closeChannel_
{ toPointer `Channel' } -> `Int' #}
-- | Close channel (but do not free memory)
closeChannel :: Channel -> IO ()
closeChannel channel = void . handleInt (Just $ channelSession channel) $ closeChannel_ channel
{# fun channel_free as freeChannel_
{ toPointer `Channel' } -> `Int' #}
-- | Free channel object's memory
freeChannel :: Channel -> IO ()
freeChannel channel = void . handleInt (Just $ channelSession channel) $ freeChannel_ channel
-- | Get channel exit status
{# fun channel_get_exit_status as channelExitStatus
{ toPointer `Channel' } -> `Int' #}
{# fun channel_get_exit_signal as channelExitSignal_
{ toPointer `Channel',
alloca- `String' peekCStringPtr*,
castPtr `Ptr Int',
alloca- `Maybe String' peekMaybeCStringPtr*,
castPtr `Ptr Int',
alloca- `Maybe String' peekMaybeCStringPtr*,
castPtr `Ptr Int' } -> `Int' #}
-- | Get channel exit signal. Returns:
-- (possibly error code, exit signal name, possibly error message, possibly language code).
channelExitSignal :: Channel -> IO (Int, String, Maybe String, Maybe String)
channelExitSignal ch = handleInt (Just $ channelSession ch) $ channelExitSignal_ ch nullPtr nullPtr nullPtr
{# fun scp_send64 as scpSendChannel_
{ toPointer `Session',
`String',
`Int',
`Int64',
round `POSIXTime',
round `POSIXTime' } -> `Ptr ()' id #}
-- | Create SCP file send channel.
scpSendChannel :: Session -> String -> Int -> Int64 -> POSIXTime -> POSIXTime -> IO Channel
scpSendChannel session remotePath mode size mtime atime = handleNullPtr (Just session) (channelFromPointer session) $
scpSendChannel_ session remotePath mode size mtime atime
type Offset = {# type off_t #}
-- {# pointer *stat_t as Stat newtype #}
-- | Create SCP file receive channel.
-- TODO: receive struct stat also.
scpReceiveChannel :: Session -> FilePath -> IO (Channel, Offset)
scpReceiveChannel s path = do
withCString path $ \pathptr ->
allocaBytes {# sizeof stat_t #} $ \statptr -> do
channel <- handleNullPtr (Just s) (channelFromPointer s) $ {# call scp_recv #} (toPointer s) pathptr statptr
size <- {# get stat_t->st_size #} statptr
return (channel, size)
-- {# fun poll_channel_read as pollChannelRead_
-- { toPointer `Channel' } -> `Int' #}
pollChannelRead :: Channel -> IO Bool
pollChannelRead ch = do
mbSocket <- sessionGetSocket (channelSession ch)
case mbSocket of
Nothing -> error "pollChannelRead without socket present"
#if MIN_VERSION_network(3,0,0)
Just _ -> pure True
#else
Just socket -> isReadable socket
#endif
--
-- | Sftp support
--
-- SFTP File Transfer Flags. See libssh2 documentation
data SftpFileTransferFlags =
FXF_READ
| FXF_WRITE
| FXF_APPEND
| FXF_CREAT
| FXF_TRUNC
| FXF_EXCL
deriving (Eq, Show)
ftf2int :: SftpFileTransferFlags -> CULong
ftf2int FXF_READ = 0x00000001
ftf2int FXF_WRITE = 0x00000002
ftf2int FXF_APPEND = 0x00000004
ftf2int FXF_CREAT = 0x00000008
ftf2int FXF_TRUNC = 0x00000010
ftf2int FXF_EXCL = 0x00000020
ftransferflags2int :: [SftpFileTransferFlags] -> CULong
ftransferflags2int list = foldr (.|.) 0 (map ftf2int list)
-- | Flags for open_ex()
data OpenExFlags = OpenFile
| OpenDir
deriving (Eq, Show)
oef2int :: (Num a) => OpenExFlags -> a
oef2int OpenFile = 0
oef2int OpenDir = 1
sftpInit :: Session -> IO Sftp
sftpInit s = handleNullPtr (Just s) (sftpFromPointer s) $
sftpInit_ s
sftpShutdown :: Sftp -> IO ()
sftpShutdown sftp =
void . handleInt (Just sftp) $ sftpShutdown_ sftp
{# fun sftp_init as sftpInit_
{ toPointer `Session' } -> `Ptr ()' id #}
{# fun sftp_shutdown as sftpShutdown_
{ toPointer `Sftp' } -> `Int' #}
-- | Open regular file handler
sftpOpenFile :: Sftp -> String -> Int -> [SftpFileTransferFlags] -> IO SftpHandle
sftpOpenFile sftp path mode flags =
handleNullPtr (Just sftp) ( sftpHandleFromPointer sftp ) $
sftpOpen_ sftp path (toEnum mode) flags (oef2int OpenFile)
-- | Open directory file handler
sftpOpenDir :: Sftp -> String -> IO SftpHandle
sftpOpenDir sftp path =
handleNullPtr (Just sftp) ( sftpHandleFromPointer sftp ) $
sftpOpen_ sftp path 0 [] (oef2int OpenDir)
sftpOpen_ :: Sftp -> String -> CLong -> [SftpFileTransferFlags] -> CInt -> IO (Ptr ())
sftpOpen_ sftp path mode fl open_type =
let flags = ftransferflags2int fl
in
withCStringLen path $ \(pathP, pathL) -> do
{# call sftp_open_ex #} (toPointer sftp) pathP (toEnum pathL) flags mode open_type
-- | Read directory from file handler
sftpReadDir :: SftpHandle -> IO (Maybe (BSS.ByteString, SftpAttributes))
sftpReadDir sftph = do
let bufflen = 512
allocaBytes bufflen $ \bufptr -> do
allocaBytes {# sizeof _LIBSSH2_SFTP_ATTRIBUTES #} $ \sftpattrptr -> do
rc <- handleInt (Just sftph) $
{# call sftp_readdir_ex #} (toPointer sftph) bufptr (fromIntegral bufflen) nullPtr 0 sftpattrptr
case rc == 0 of
False -> do
fstat <- parseSftpAttributes sftpattrptr
filename <- BSS.packCStringLen (bufptr, intResult rc)
return $ Just (filename, fstat)
True ->
return Nothing
-- | Close file handle
sftpCloseHandle :: SftpHandle -> IO ()
sftpCloseHandle sftph =
void . handleInt (Just $ sftpHandleSession sftph) $
{# call sftp_close_handle #} (toPointer sftph)
data RenameFlag =
RENAME_OVERWRITE
| RENAME_ATOMIC
| RENAME_NATIVE
deriving (Eq, Show)
rf2long :: RenameFlag -> CLong
rf2long RENAME_OVERWRITE = 0x00000001
rf2long RENAME_ATOMIC = 0x00000002
rf2long RENAME_NATIVE = 0x00000004
renameFlag2int :: [RenameFlag] -> CLong
renameFlag2int flags = foldr (.|.) 0 (map rf2long flags)
-- | Rename a file on the sftp server
sftpRenameFile :: Sftp -- ^ Opened sftp session
-> FilePath -- ^ Old file name
-> FilePath -- ^ New file name
-> IO ()
sftpRenameFile sftp src dest =
sftpRenameFileEx sftp src dest [ RENAME_NATIVE, RENAME_ATOMIC, RENAME_OVERWRITE]
-- | Rename a file on the sftp server
sftpRenameFileEx :: Sftp -- ^ Opened sftp session
-> FilePath -- ^ Old file name
-> FilePath -- ^ New file name
-> [RenameFlag] -- ^ Rename flags
-> IO ()
sftpRenameFileEx sftp src dest flags =
withCStringLen src $ \(srcP, srcL) ->
withCStringLen dest $ \(destP, destL) ->
void . handleInt (Just $ sftpSession sftp) $
{# call sftp_rename_ex #} (toPointer sftp) srcP (toEnum srcL) destP (toEnum destL) (renameFlag2int flags )
-- | Download file from the sftp server
sftpReadFileToHandler :: SftpHandle -> Handle -> Int -> IO Int
sftpReadFileToHandler sftph fh fileSize =
let
go :: Int -> Ptr a -> IO Int
go received buffer = do
let toRead :: Int
toRead = min (fromIntegral fileSize - received) bufferSize
sz <- receive toRead buffer 0
_ <- hPutBuf fh buffer sz
let newreceived :: Int
newreceived = (received + fromIntegral sz)
if newreceived < fromIntegral fileSize
then go newreceived buffer
else return $ fromIntegral newreceived
receive :: Int -> Ptr a -> Int -> IO Int
receive 0 _ read_sz = return read_sz
receive toread buf alreadyread = do
received <- handleInt (Just sftph)
$ {# call sftp_read #} (toPointer sftph)
(buf `plusPtr` alreadyread)
(fromIntegral toread)
receive (toread - fromIntegral received) buf (alreadyread + fromIntegral received)
bufferSize = 0x100000
in allocaBytes bufferSize $ go 0
-- | Upload file to the sftp server
sftpWriteFileFromHandler :: SftpHandle -> Handle -> IO Integer
sftpWriteFileFromHandler sftph fh =
let
go :: Integer -> Ptr a -> IO Integer
go done buffer = do
sz <- hGetBuf fh buffer bufferSize
send 0 (fromIntegral sz) buffer
let newDone = done + fromIntegral sz
if sz < bufferSize
then return newDone
else go newDone buffer
send :: Int -> CLong -> Ptr a -> IO ()
send _ 0 _ = return ()
send written size buf = do
sent <- handleInt (Just sftph)
$ {# call sftp_write #} (toPointer sftph)
(buf `plusPtr` written)
(fromIntegral size)
send (written + fromIntegral sent) (size - fromIntegral sent) buf
bufferSize :: Int
bufferSize = 0x100000
in allocaBytes bufferSize $ go 0
-- | Upload bytes to the sftp server
-- Returns size of sent data.
sftpWriteFileFromBytes :: SftpHandle -> BSS.ByteString -> IO Integer
sftpWriteFileFromBytes sftph bs = BSS.useAsCStringLen bs (uncurry (send 0))
where
send :: Int -> Ptr CChar -> Int -> IO Integer
send written _ 0 = pure (toInteger written)
send written src len = do
let nBytes = min len bufferSize
sent <- fmap fromIntegral . handleInt (Just sftph)
$ {# call sftp_write #} (toPointer sftph)
src
(fromIntegral nBytes)
send (written + sent) (src `plusPtr` written) (len - sent)
bufferSize :: Int
bufferSize = 0x100000
data SftpAttributes = SftpAttributes {
saFlags :: CULong,
saFileSize :: CULLong,
saUid :: CULong,
saGid :: CULong,
saPermissions :: CULong,
saAtime :: CULong,
saMtime :: CULong
} deriving (Show, Eq)
-- | Get sftp attributes from the sftp handler
sftpFstat :: SftpHandle
-> IO (SftpAttributes)
sftpFstat sftph = do
allocaBytes {# sizeof _LIBSSH2_SFTP_ATTRIBUTES #} $ \sftpattrptr -> do
_ <- handleInt (Just sftph) $
{# call sftp_fstat_ex #} (toPointer sftph) sftpattrptr 0
parseSftpAttributes sftpattrptr
parseSftpAttributes :: Ptr a -> IO SftpAttributes -- TODO why not storable?
parseSftpAttributes sftpattrptr = do
flags<- {# get _LIBSSH2_SFTP_ATTRIBUTES->flags #} sftpattrptr
size <- {# get _LIBSSH2_SFTP_ATTRIBUTES->filesize #} sftpattrptr
uid <- {# get _LIBSSH2_SFTP_ATTRIBUTES->uid #} sftpattrptr
gid <- {# get _LIBSSH2_SFTP_ATTRIBUTES->gid #} sftpattrptr
perm <- {# get _LIBSSH2_SFTP_ATTRIBUTES->permissions #} sftpattrptr
atime<- {# get _LIBSSH2_SFTP_ATTRIBUTES->atime #} sftpattrptr
mtime<- {# get _LIBSSH2_SFTP_ATTRIBUTES->mtime #} sftpattrptr
return $ SftpAttributes flags size uid gid perm atime mtime
-- | Delete file from SFTP server
sftpDeleteFile :: Sftp -- ^ Opened sftp session
-> FilePath -- ^ Path to the file to be deleted
-> IO ()
sftpDeleteFile sftp path = do
withCStringLen path $ \(str,len) -> do
void . handleInt (Just sftp) $
{# call sftp_unlink_ex #} (toPointer sftp) str (toEnum len)
--
-- | Agent support
--
-- | Initialize a new ssh agent handle.
agentInit :: Session -> IO Agent
agentInit s = handleNullPtr (Just s) (agentFromPointer s) $ agentInit_ s
{# fun agent_init as agentInit_ { toPointer `Session' } -> `Ptr ()' id #}
{# fun agent_free as agentFree
{ toPointer `Agent' } -> `()' #}
-- | Attempt to establish a connection to an ssh agent process.
-- | The environment variable @SSH_AUTH_SOCK@ is used to determine where to connect on unix.
agentConnect :: Agent -> IO ()
agentConnect agent = void . handleInt (Just agent) $ agentConnect_ agent
{# fun agent_connect as agentConnect_ { toPointer `Agent' } -> `Int' #}
-- | Get or update the list of known identities. Must be called at least once.
agentListIdentities :: Agent -> IO ()
agentListIdentities agent = void . handleInt (Just agent) $ agentListIdentities_ agent
{# fun agent_list_identities as agentListIdentities_ { toPointer `Agent' } -> `Int' #}
-- | Cleans up a connection to an ssh agent.
agentDisconnect :: Agent -> IO ()
agentDisconnect agent = void . handleInt (Just agent) $ agentDisconnect_ agent
{# fun agent_disconnect as agentDisconnect_ { toPointer `Agent' } -> `Int' #}
-- | Copies all the keys from the agent to the local process.
agentGetIdentities :: Agent -> IO [AgentPublicKey]
agentGetIdentities agent = agentGetIdentities_ agent []
where
agentGetIdentities_ :: Agent -> [AgentPublicKey] -> IO [AgentPublicKey]
agentGetIdentities_ agent' acc@[] = do
k <- agentGetIdentity agent' Nothing
case k of
Just aKey -> agentGetIdentities_ agent' [aKey]
Nothing -> return acc
agentGetIdentities_ agent' acc = do
k <- agentGetIdentity agent' $ Just $ head acc
case k of
Just aKey -> agentGetIdentities_ agent' (aKey:acc)
Nothing -> return acc
agentNullPublicKey :: IO AgentPublicKey
agentNullPublicKey = agentPublicKeyFromPointer nullPtr
-- | Copies one identity from the agent to the local process.
agentGetIdentity :: Agent -- ^ Agent handle.
-> Maybe AgentPublicKey -- ^ Previous key returned.
-> IO (Maybe AgentPublicKey)
agentGetIdentity agent Nothing = do
nullKey <- agentNullPublicKey
agentGetIdentity agent (Just nullKey)
agentGetIdentity agent (Just key) = do
agentGetIdentity_ agent key
agentGetIdentity_ :: Agent -> AgentPublicKey -> IO (Maybe AgentPublicKey)
agentGetIdentity_ a pk = do
withAgentPublicKey pk $ \pkPtr -> do
alloca $ \ptr -> do
(res, pptr) <- with ptr $ \pkStore -> do
x <- {# call agent_get_identity #} (toPointer a) pkStore (castPtr pkPtr)
pptr <- peek pkStore
return (x, pptr)
void $ handleInt (Just a) (return res)
if res == 0
then do
resPkPtr <- agentPublicKeyFromPointer pptr
return $ Just resPkPtr
else return Nothing
-- | Return the comment from the given agent public key.
agentPublicKeyComment :: AgentPublicKey -> IO BSS.ByteString
agentPublicKeyComment pk = do
withAgentPublicKey pk $ \pkPtr -> do
c <- {# get struct agent_publickey->comment #} pkPtr
BSS.packCString c
-- | Return the bytes of the given agent public key.
agentPublicKeyBlob :: AgentPublicKey -> IO BSS.ByteString
agentPublicKeyBlob pk = do
withAgentPublicKey pk $ \pkPtr -> do
blobPtr <- {# get struct agent_publickey->blob #} pkPtr
blobLen <- {# get struct agent_publickey->blob_len #} pkPtr
BSS.packCStringLen (castPtr blobPtr, fromEnum blobLen)
-- | Perform agent based public key authentication.
-- You almost certainly want @agentAuthenticate instead of this, since this
-- only does one round of authentication with the agent.
agentUserAuth :: Agent -- ^ Agent handle.
-> String -- ^ Username to authenticate with.
-> AgentPublicKey -- ^ Public key to use from the agent.
-> IO ()
agentUserAuth agent username key = void . handleInt (Just agent) $ agentUserAuth_ agent username key
{# fun agent_userauth as agentUserAuth_ { toPointer `Agent'
, `String'
, withAgentPublicKeyVoidPtr* `AgentPublicKey'
} -> `Int' #}
-- | Authenticate with an ssh agent.
-- Takes a user and an agent and tries each key from the agent in succession.
-- Throws AUTHENTICATION_FAILED if it's unable to authenticate.
-- If you call this, you need to call @agentListIdentities at least once.
agentAuthenticate :: String -- ^ Remote user name.
-> Agent -- ^ Connection to an agent.
-> IO ()
agentAuthenticate login agent = do
firstKey <- agentGetIdentity agent Nothing
agentAuthenticate' login agent firstKey
where
agentAuthenticate' _ _ Nothing = throw AUTHENTICATION_FAILED
agentAuthenticate' u a (Just k) = do
r <- tryJust isAuthenticationFailed (agentUserAuth a u k)
case r of
Left _ -> do
nextKey <- agentGetIdentity a $ Just k
agentAuthenticate' u a nextKey
Right _ -> return ()
isAuthenticationFailed AUTHENTICATION_FAILED = Just ()
isAuthenticationFailed _ = Nothing
withAgentPublicKeyVoidPtr :: AgentPublicKey -> (Ptr () -> IO a) -> IO a
withAgentPublicKeyVoidPtr p f = withAgentPublicKey p $ \pp -> f (castPtr pp)