{"id":18579022,"url":"https://github.com/lpeterse/haskell-ssh","last_synced_at":"2025-04-10T10:31:24.812Z","repository":{"id":59153123,"uuid":"114385224","full_name":"lpeterse/haskell-ssh","owner":"lpeterse","description":"An SSH implemenation in pure Haskell","archived":false,"fork":false,"pushed_at":"2022-02-14T15:59:12.000Z","size":640,"stargazers_count":16,"open_issues_count":4,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-24T19:39:46.639Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://hackage.haskell.org/package/hssh","language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lpeterse.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-12-15T15:33:54.000Z","updated_at":"2025-03-12T20:53:59.000Z","dependencies_parsed_at":"2022-09-13T11:01:13.093Z","dependency_job_id":null,"html_url":"https://github.com/lpeterse/haskell-ssh","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lpeterse%2Fhaskell-ssh","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lpeterse%2Fhaskell-ssh/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lpeterse%2Fhaskell-ssh/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lpeterse%2Fhaskell-ssh/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lpeterse","download_url":"https://codeload.github.com/lpeterse/haskell-ssh/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248199136,"owners_count":21063641,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-06T23:38:43.531Z","updated_at":"2025-04-10T10:31:24.114Z","avatar_url":"https://github.com/lpeterse.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"hssh - Haskell SSH [![Hackage](https://img.shields.io/github/release/lpeterse/haskell-ssh.svg)](https://github.com/lpeterse/haskell-ssh/releases) [![Travis](https://img.shields.io/travis/lpeterse/haskell-ssh.svg)](https://travis-ci.org/lpeterse/haskell-ssh)\n=======================\n\n## Introduction\n\nThis library is a pure-Haskell implementation of the SSH2 protocol.\n\n## Features\n\nBy now, only the server part has been implemented. It can be used\nto embed SSH servers into Haskell applications.\n\nTransport layer:\n\n- `ssh-ed25519` host keys.\n- Key exchange using the `curve25519-sha256@libssh.org` algorithm.\n- Encryption using the  `chacha20-poly1305@openssh.com` algorithm.\n- Rekeying fully supported and compatible with OpenSSH.\n\nAuthentication layer:\n\n- User authentication with `ssh-ed25519` public keys.\n\nConnection layer:\n\n- Connection multiplexing.\n- Serving session requests (shell and exec) with user-supplied handlers.\n- Serving direct-tcpip requests with user-supplied handlers.\n\nMisc:\n\n- SSH private key file import (not encrypted yet).\n\n## Dependencies\n\n- async\n- base\n- bytestring\n- cereal\n- containers\n- cryptonite\n- memory\n- stm\n- data-default\n\n## Example server application\n\n```hs\n{-# LANGUAGE FlexibleContexts  #-}\n{-# LANGUAGE FlexibleInstances #-}\n{-# LANGUAGE OverloadedStrings #-}\nmodule Main where\n\nimport           Control.Concurrent             ( forkFinally\n                                                )\nimport           Control.Exception              ( bracket\n                                                , bracketOnError\n                                                , handle\n                                                , throwIO\n                                                )\nimport           Control.Monad                  ( forever\n                                                , void\n                                                )\nimport qualified Data.ByteArray                as BA\nimport qualified Data.ByteString               as BS\nimport           Data.Default\nimport           System.Exit\n\nimport qualified System.Socket                  as S\nimport qualified System.Socket.Family.Inet6     as S\nimport qualified System.Socket.Protocol.Default as S\nimport qualified System.Socket.Type.Stream      as S\nimport qualified System.Socket.Unsafe           as S\n\nimport           Network.SSH\nimport qualified Network.SSH.Server            as Server\n\nmain :: IO ()\nmain = do\n    file                \u003c- BS.readFile \"./resources/id_ed25519\"\n    (privateKey, _) : _ \u003c- decodePrivateKeyFile BS.empty file :: IO [(KeyPair, BA.Bytes)]\n    bracket open close (accept config privateKey)\n  where\n    config = def\n        { Server.transportConfig          = def\n        , Server.userAuthConfig           = def\n            { Server.onAuthRequest        = \\username _ _ -\u003e pure (Just username)\n            }\n        , Server.connectionConfig         = def\n            { Server.onSessionRequest     = handleSessionRequest\n            , Server.onDirectTcpIpRequest = handleDirectTcpIpRequest\n            }\n        }\n    open  = S.socket :: IO (S.Socket S.Inet6 S.Stream S.Default)\n    close = S.close\n    accept config agent s = do\n        S.setSocketOption s (S.ReuseAddress True)\n        S.setSocketOption s (S.V6Only False)\n        S.bind s (S.SocketAddressInet6 S.inet6Any 22 0 0)\n        S.listen s 5\n        forever $ bracketOnError (S.accept s) (S.close . fst) $ \\(stream, peer) -\u003e do\n            putStrLn $ \"Connection from \" ++ show peer\n            void $ forkFinally\n                (Server.serve config agent stream \u003e\u003e= print)\n                (const $ S.close stream)\n\nhandleDirectTcpIpRequest :: identity -\u003e Server.DirectTcpIpRequest -\u003e IO (Maybe Server.DirectTcpIpHandler)\nhandleDirectTcpIpRequest idnt req = pure $ Just $ Server.DirectTcpIpHandler $ \\stream-\u003e do\n    bs \u003c- receive stream 4096\n    sendAll stream \"HTTP/1.1 200 OK\\n\"\n    sendAll stream \"Content-Type: text/plain\\n\\n\"\n    sendAll stream $! BS.pack $ fmap (fromIntegral . fromEnum) $ show req\n    sendAll stream \"\\n\\n\"\n    sendAll stream bs\n    print bs\n\nhandleSessionRequest :: identity -\u003e Server.SessionRequest -\u003e IO (Maybe Server.SessionHandler)\nhandleSessionRequest idnt req = pure $ Just $ Server.SessionHandler $ \\_ _ _ _ stdout _ -\u003e do\n    sendAll stdout \"Hello world!\\n\"\n    pure ExitSuccess\n\n-------------------------------------------------------------------------------\n-- Instances for use with the socket library\n-------------------------------------------------------------------------------\n\ninstance DuplexStream (S.Socket f S.Stream p) where\n\ninstance OutputStream  (S.Socket f S.Stream p) where\n    send stream bytes =\n        handle f $ S.send stream bytes S.msgNoSignal\n        where\n            f e\n                | e == S.ePipe = pure 0\n                | otherwise    = throwIO e\n    sendUnsafe stream (BA.MemView ptr n) = fromIntegral \u003c$\u003e\n        handle f (S.unsafeSend stream ptr (fromIntegral n) S.msgNoSignal)\n        where\n            f e\n                | e == S.ePipe = pure 0\n                | otherwise    = throwIO e\n\ninstance InputStream  (S.Socket f S.Stream p) where\n    peek stream len = S.receive stream len (S.msgNoSignal \u003c\u003e S.msgPeek)\n    receive stream len = S.receive stream len S.msgNoSignal\n    receiveUnsafe stream (BA.MemView ptr n) = fromIntegral \u003c$\u003e\n        S.unsafeReceive stream ptr (fromIntegral n) S.msgNoSignal\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flpeterse%2Fhaskell-ssh","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flpeterse%2Fhaskell-ssh","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flpeterse%2Fhaskell-ssh/lists"}