1
0
Fork 0
mirror of https://code.sup39.dev/repos/Wqawg synced 2025-01-09 07:06:46 +09:00
vervis/src/Vervis/Ssh.hs

199 lines
6.3 KiB
Haskell
Raw Normal View History

2016-03-06 20:58:48 +09:00
{- This file is part of Vervis.
-
- Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
- The author(s) have dedicated all copyright and related and neighboring
- rights to this software to the public domain worldwide. This software is
- distributed without any warranty.
-
- You should have received a copy of the CC0 Public Domain Dedication along
- with this software. If not, see
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
module Vervis.Ssh
( runSsh
)
where
import Prelude
import Control.Applicative ((<|>), optional)
2016-03-06 20:58:48 +09:00
import Control.Monad (when)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Logger
2016-03-06 20:58:48 +09:00
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.Reader (ReaderT (runReaderT), ask)
import Data.Attoparsec.Text
import Data.ByteString (ByteString)
2016-03-06 20:58:48 +09:00
import Data.ByteString.Lazy (fromStrict)
import Data.Foldable (find)
import Data.Text (Text)
2016-03-06 20:58:48 +09:00
import Database.Persist
import Database.Persist.Sql
2016-03-06 20:58:48 +09:00
import Network.SSH
import Network.SSH.Channel
import Network.SSH.Crypto
import Network.SSH.Session
import qualified Data.Text as T
2016-03-06 20:58:48 +09:00
import Vervis.Model
import Vervis.Settings
-------------------------------------------------------------------------------
-- Types
-------------------------------------------------------------------------------
type ChannelBase = LoggingT (ReaderT ConnectionPool IO)
type SessionBase = LoggingT (ReaderT ConnectionPool IO)
type UserAuthId = PersonId
2016-03-06 20:58:48 +09:00
type Channel = ChannelT UserAuthId ChannelBase
type Session = SessionT SessionBase UserAuthId ChannelBase
type SshChanDB = SqlPersistT Channel
type SshSessDB = SqlPersistT Session
2016-03-06 20:58:48 +09:00
data RepoSpec
= SpecUserRepo Text Text
| SpecRepo Text
deriving Show
data Action = UploadPack RepoSpec deriving Show
-------------------------------------------------------------------------------
-- Utils
-------------------------------------------------------------------------------
src :: Text
src = "SSH"
2016-03-06 20:58:48 +09:00
runChanDB :: SshChanDB a -> Channel a
runChanDB action = do
pool <- lift . lift $ ask
2016-03-06 20:58:48 +09:00
runSqlPool action pool
runSessDB :: SshSessDB a -> Session a
runSessDB action = do
pool <- lift . lift $ ask
2016-03-06 20:58:48 +09:00
runSqlPool action pool
chanFail :: Bool -> Text -> Channel ()
2016-03-06 20:58:48 +09:00
chanFail wantReply msg = do
channelError $ T.unpack msg
2016-03-06 20:58:48 +09:00
when wantReply channelFail
-------------------------------------------------------------------------------
-- Auth
-------------------------------------------------------------------------------
authorize :: Authorize -> Session (AuthResult UserAuthId)
authorize (Password _ _) = return AuthFail
2016-03-06 20:58:48 +09:00
authorize (PublicKey name key) = do
mpk <- runSessDB $ do
mp <- getBy $ UniquePersonLogin $ T.pack name
2016-03-06 20:58:48 +09:00
case mp of
Nothing -> return Nothing
Just (Entity pid _p) -> do
ks <- selectList [SshKeyPerson ==. pid] []
return $ Just (pid, ks)
case mpk of
2016-03-06 20:58:48 +09:00
Nothing -> do
lift $ $logInfoS src "Auth failed: Invalid user"
return AuthFail
Just (pid, keys) -> do
2016-03-06 20:58:48 +09:00
let eValue (Entity _ v) = v
matches =
(== key) . blobToKey . fromStrict . sshKeyContent . eValue
case find matches keys of
Nothing -> do
lift $ $logInfoS src "Auth failed: No matching key found"
return AuthFail
2016-03-06 20:58:48 +09:00
Just match -> do
lift $ $logInfoS src "Auth succeeded"
return $ AuthSuccess pid
2016-03-06 20:58:48 +09:00
-------------------------------------------------------------------------------
-- Actions
-------------------------------------------------------------------------------
repoSpecP :: Parser RepoSpec
repoSpecP =
SpecRepo <$> (msh *> part)
<|> SpecUserRepo <$> (msh *> part) <* char '/' <*> part
where
part = takeWhile1 $ \ c -> c /= '/' && c /= '\''
msh = optional (satisfy $ \ c -> c == '/' || c == '~')
actionP :: Parser Action
actionP = UploadPack <$> ("git-upload-pack '" *> repoSpecP <* char '\'')
parseExec :: Text -> Either String Action
parseExec input = parseOnly (actionP <* endOfInput) input
detectAction :: ChannelRequest -> Either Text Action
detectAction (Execute s) =
case parseExec $ T.pack s of
Left _ -> Left "Unsupported command"
Right action -> Right action
detectAction _ = Left "Unsupported channel request"
runAction :: Bool -> Action -> Channel (Maybe Text)
runAction _wantReply action =
case action of
UploadPack repo -> return $ Just "Doesn't work yet"
2016-03-06 20:58:48 +09:00
handle :: Bool -> ChannelRequest -> Channel ()
handle wantReply request = do
lift $ $logDebugS src $ T.pack $ show request
case detectAction request of
Left e -> err e
Right act -> do
lift $ $logDebugS src $ T.pack $ show act
res <- runAction wantReply act
case res of
Nothing -> do
when wantReply channelSuccess
channelDone
Just msg -> err msg
where
err = chanFail wantReply
2016-03-06 20:58:48 +09:00
-------------------------------------------------------------------------------
-- Config and running
-------------------------------------------------------------------------------
ready :: LogFunc -> IO ()
ready = runLoggingT $ $logInfoS src "SSH server component starting"
2016-03-06 20:58:48 +09:00
mkConfig
:: AppSettings
-> ConnectionPool
-> LogFunc
-> IO (Config SessionBase ChannelBase UserAuthId)
mkConfig settings pool logFunc = do
2016-03-06 20:58:48 +09:00
keyPair <- keyPairFromFile $ appSshKeyFile settings
return $ Config
{ cSession = SessionConfig
{ scAuthMethods = ["publickey"]
, scAuthorize = authorize
, scKeyPair = keyPair
, scRunBaseMonad =
flip runReaderT pool . flip runLoggingT logFunc
2016-03-06 20:58:48 +09:00
}
, cChannel = ChannelConfig
{ ccRequestHandler = handle
, ccRunBaseMonad =
flip runReaderT pool . flip runLoggingT logFunc
2016-03-06 20:58:48 +09:00
}
, cPort = fromIntegral $ appSshPort settings
, cReadyAction = ready logFunc
2016-03-06 20:58:48 +09:00
}
runSsh :: AppSettings -> ConnectionPool -> LogFunc -> IO ()
runSsh settings pool logFunc = do
config <- mkConfig settings pool logFunc
2016-03-06 20:58:48 +09:00
startConfig config