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.Monad (when)
|
|
|
|
import Control.Monad.IO.Class (liftIO)
|
2016-03-10 07:27:25 +09:00
|
|
|
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)
|
2016-04-01 14:00:02 +09:00
|
|
|
import Data.ByteString (ByteString)
|
2016-03-06 20:58:48 +09:00
|
|
|
import Data.ByteString.Lazy (fromStrict)
|
|
|
|
import Data.Foldable (find)
|
2016-04-01 14:00:02 +09:00
|
|
|
import Data.Text (Text, pack, unpack)
|
2016-03-06 20:58:48 +09:00
|
|
|
import Database.Persist
|
|
|
|
import Database.Persist.Sql (SqlBackend, ConnectionPool, runSqlPool)
|
|
|
|
import Network.SSH
|
|
|
|
import Network.SSH.Channel
|
|
|
|
import Network.SSH.Crypto
|
|
|
|
import Network.SSH.Session
|
2016-03-10 07:27:25 +09:00
|
|
|
import Yesod.Default.Main (LogFunc)
|
2016-03-06 20:58:48 +09:00
|
|
|
|
|
|
|
import Vervis.Model
|
|
|
|
import Vervis.Settings
|
|
|
|
|
2016-03-10 07:27:25 +09:00
|
|
|
-- TODO:
|
2016-04-01 14:00:02 +09:00
|
|
|
-- [ ] See which git commands gitolite SSH supports and see if I can implement
|
2016-03-10 07:27:25 +09:00
|
|
|
-- them with Hit (i think it was git upload-pack)
|
|
|
|
|
2016-04-01 14:00:02 +09:00
|
|
|
type ChannelBase = LoggingT (ReaderT ConnectionPool IO)
|
|
|
|
type SessionBase = LoggingT (ReaderT ConnectionPool IO)
|
2016-04-10 00:45:00 +09:00
|
|
|
--type UserAuthId = PersonId
|
2016-03-06 20:58:48 +09:00
|
|
|
type Backend = SqlBackend
|
|
|
|
|
2016-04-10 00:45:00 +09:00
|
|
|
type Channel = ChannelT {-UserAuthId-} ChannelBase
|
|
|
|
type Session = SessionT SessionBase {-UserAuthId-} ChannelBase
|
2016-03-06 20:58:48 +09:00
|
|
|
type SshChanDB = ReaderT Backend Channel
|
|
|
|
type SshSessDB = ReaderT Backend Session
|
|
|
|
|
2016-03-10 07:27:25 +09:00
|
|
|
src :: Text
|
|
|
|
src = "SSH"
|
|
|
|
|
2016-03-06 20:58:48 +09:00
|
|
|
runChanDB :: SshChanDB a -> Channel a
|
|
|
|
runChanDB action = do
|
2016-03-10 07:27:25 +09:00
|
|
|
pool <- lift . lift $ ask
|
2016-03-06 20:58:48 +09:00
|
|
|
runSqlPool action pool
|
|
|
|
|
|
|
|
runSessDB :: SshSessDB a -> Session a
|
|
|
|
runSessDB action = do
|
2016-03-10 07:27:25 +09:00
|
|
|
pool <- lift . lift $ ask
|
2016-03-06 20:58:48 +09:00
|
|
|
runSqlPool action pool
|
|
|
|
|
2016-04-01 14:00:02 +09:00
|
|
|
chanFail :: Bool -> Text -> Channel ()
|
2016-03-06 20:58:48 +09:00
|
|
|
chanFail wantReply msg = do
|
|
|
|
channelError $ unpack msg
|
|
|
|
when wantReply channelFail
|
|
|
|
|
2016-04-10 00:45:00 +09:00
|
|
|
authorize :: Authorize -> Session Bool -- (AuthResult UserAuthId)
|
|
|
|
authorize (Password _ _) = return False -- AuthFail
|
2016-03-06 20:58:48 +09:00
|
|
|
authorize (PublicKey name key) = do
|
2016-04-09 06:10:33 +09:00
|
|
|
mpk <- runSessDB $ do
|
2016-03-06 20:58:48 +09:00
|
|
|
mp <- getBy $ UniquePersonLogin $ pack name
|
|
|
|
case mp of
|
|
|
|
Nothing -> return Nothing
|
2016-04-09 06:10:33 +09:00
|
|
|
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
|
2016-03-10 07:27:25 +09:00
|
|
|
$logInfoS src "Auth failed: Invalid user"
|
2016-04-10 00:45:00 +09:00
|
|
|
return False -- AuthFail
|
2016-04-09 06:10:33 +09:00
|
|
|
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
|
2016-03-10 07:27:25 +09:00
|
|
|
$logInfoS src "Auth failed: No matching key found"
|
2016-04-10 00:45:00 +09:00
|
|
|
return False -- AuthFail
|
2016-03-06 20:58:48 +09:00
|
|
|
Just match -> do
|
2016-03-10 07:27:25 +09:00
|
|
|
$logInfoS src "Auth succeeded"
|
2016-04-10 00:45:00 +09:00
|
|
|
return True -- $ AuthSuccess pid
|
2016-03-06 20:58:48 +09:00
|
|
|
|
2016-04-01 14:00:02 +09:00
|
|
|
data Action = UploadPack () deriving Show
|
|
|
|
|
|
|
|
detectAction :: ChannelRequest -> Maybe Action
|
|
|
|
detectAction _ = Nothing
|
|
|
|
|
|
|
|
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
|
2016-03-10 07:38:28 +09:00
|
|
|
$logDebugS src $ pack $ show request
|
2016-04-01 14:00:02 +09:00
|
|
|
case detectAction request of
|
|
|
|
Nothing -> err "Unsupported request"
|
|
|
|
Just act -> do
|
|
|
|
$logDebugS src $ 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
|
|
|
|
2016-03-10 07:27:25 +09:00
|
|
|
ready :: LogFunc -> IO ()
|
|
|
|
ready = runLoggingT $ $logInfoS src "SSH server component starting"
|
2016-03-06 20:58:48 +09:00
|
|
|
|
2016-03-10 07:27:25 +09:00
|
|
|
mkConfig
|
|
|
|
:: AppSettings
|
|
|
|
-> ConnectionPool
|
|
|
|
-> LogFunc
|
2016-04-10 00:45:00 +09:00
|
|
|
-> IO (Config SessionBase ChannelBase {-UserAuthId-})
|
2016-03-10 07:27:25 +09:00
|
|
|
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
|
2016-03-10 07:27:25 +09:00
|
|
|
, scRunBaseMonad =
|
|
|
|
flip runReaderT pool . flip runLoggingT logFunc
|
2016-03-06 20:58:48 +09:00
|
|
|
}
|
|
|
|
, cChannel = ChannelConfig
|
|
|
|
{ ccRequestHandler = handle
|
2016-03-10 07:27:25 +09:00
|
|
|
, ccRunBaseMonad =
|
|
|
|
flip runReaderT pool . flip runLoggingT logFunc
|
2016-03-06 20:58:48 +09:00
|
|
|
}
|
|
|
|
, cPort = fromIntegral $ appSshPort settings
|
2016-03-10 07:27:25 +09:00
|
|
|
, cReadyAction = ready logFunc
|
2016-03-06 20:58:48 +09:00
|
|
|
}
|
|
|
|
|
2016-03-10 07:27:25 +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
|