| 1 |
module Network.CubeCotillion where
|
| 2 |
|
| 3 |
import Control.Concurrent (forkIO)
|
| 4 |
import Control.Monad.IO.Class(MonadIO(..))
|
| 5 |
import Data.ByteString (ByteString)
|
| 6 |
import qualified Data.ByteString as BS
|
| 7 |
import Data.ByteString.Char8 (pack, unpack)
|
| 8 |
import GHC.Exts (IsString(..))
|
| 9 |
import MonadLib
|
| 10 |
import Network
|
| 11 |
import Network.SSH.Server
|
| 12 |
|
| 13 |
type Key = [ServerCredential]
|
| 14 |
|
| 15 |
loadKey = loadPrivateKeys
|
| 16 |
|
| 17 |
cubeCotillion :: Int -> Key -> CubeM () -> IO ()
|
| 18 |
cubeCotillion port keys cube = withSocketsDo $ do
|
| 19 |
let routes = getRoutes cube
|
| 20 |
sock <- listenOn (PortNumber (fromIntegral port))
|
| 21 |
let server = Server
|
| 22 |
{ sAccept = mkHandlers sock routes
|
| 23 |
, sAuthenticationAlgs = keys
|
| 24 |
, sVersion = "CubeCotillion_0.0"
|
| 25 |
, sDebugLevel = 0
|
| 26 |
}
|
| 27 |
sshServer server
|
| 28 |
|
| 29 |
mkHandlers :: Socket -> [Route] -> IO (SessionHandlers, HandleLike)
|
| 30 |
mkHandlers sock routes = do
|
| 31 |
(handle, _, _) <- accept sock
|
| 32 |
let handlers = SessionHandlers
|
| 33 |
{ cOpenShell = \ _ _ _ _ _ -> return False
|
| 34 |
, cDirectTcp = \ _ _ _ _ -> return False
|
| 35 |
, cRequestSubsystem = \ _ _ _ -> return False
|
| 36 |
, cAuthHandler = \ _ _ _ _ -> return AuthAccepted
|
| 37 |
, cRequestExec = \ cmd _ write -> do
|
| 38 |
_ <- forkIO $ do
|
| 39 |
case dispatchRoutes routes cmd of
|
| 40 |
Just action -> action write >> write Nothing
|
| 41 |
Nothing -> write Nothing
|
| 42 |
return True
|
| 43 |
}
|
| 44 |
return (handlers, handle2HandleLike handle)
|
| 45 |
|
| 46 |
cmd :: CommandPattern -> ActionM () -> CubeM ()
|
| 47 |
cmd command action = CubeM (put [Route command action])
|
| 48 |
|
| 49 |
bs :: ByteString -> ActionM ()
|
| 50 |
bs val = do
|
| 51 |
(_, write) <- ActionM ask
|
| 52 |
ActionM $ inBase $ write (Just val)
|
| 53 |
|
| 54 |
string :: String -> ActionM ()
|
| 55 |
string str = do
|
| 56 |
(_, write) <- ActionM ask
|
| 57 |
ActionM $ inBase $ write (Just (pack str))
|
| 58 |
|
| 59 |
param :: ByteString -> ActionM ByteString
|
| 60 |
param name = do
|
| 61 |
(vars, _) <- ActionM ask
|
| 62 |
let Just val = lookup name vars
|
| 63 |
return val
|
| 64 |
|
| 65 |
readParam :: Read a => ByteString -> ActionM a
|
| 66 |
readParam name = do
|
| 67 |
r <- param name
|
| 68 |
return (read (unpack r))
|
| 69 |
|
| 70 |
data Fragment
|
| 71 |
= Word ByteString
|
| 72 |
| Var ByteString
|
| 73 |
deriving (Eq, Show)
|
| 74 |
|
| 75 |
newtype CommandPattern = CommandPattern
|
| 76 |
{ commandChunks :: [Fragment] } deriving (Eq, Show)
|
| 77 |
|
| 78 |
instance IsString CommandPattern where
|
| 79 |
fromString str =
|
| 80 |
CommandPattern
|
| 81 |
[ if BS.head c == 58
|
| 82 |
then Var (BS.tail c)
|
| 83 |
else Word c
|
| 84 |
| c <- BS.split 32 (pack str)
|
| 85 |
]
|
| 86 |
|
| 87 |
match :: ByteString -> CommandPattern -> Maybe [(ByteString, ByteString)]
|
| 88 |
match bs cmd = go (BS.split 32 bs) (commandChunks cmd)
|
| 89 |
where go [] [] = Just []
|
| 90 |
go (b:bs) (Var t:ts) =
|
| 91 |
((t, b):) `fmap` go bs ts
|
| 92 |
go (b:bs) (Word t:ts)
|
| 93 |
| b == t = go bs ts
|
| 94 |
| otherwise = Nothing
|
| 95 |
go _ _ = Nothing
|
| 96 |
|
| 97 |
data Route = Route
|
| 98 |
{ routePattern :: CommandPattern
|
| 99 |
, routeAction :: ActionM ()
|
| 100 |
}
|
| 101 |
|
| 102 |
getRoutes :: CubeM () -> [Route]
|
| 103 |
getRoutes = snd . runId . runWriterT . runCubeM
|
| 104 |
|
| 105 |
dispatchRoutes :: [Route] -> ByteString -> Maybe (Writer -> IO ())
|
| 106 |
dispatchRoutes [] _ = Nothing
|
| 107 |
dispatchRoutes (r:rs) bs =
|
| 108 |
case match bs (routePattern r) of
|
| 109 |
Nothing -> dispatchRoutes rs bs
|
| 110 |
Just vs -> Just (runActionWith vs (routeAction r))
|
| 111 |
|
| 112 |
newtype CubeM a = CubeM
|
| 113 |
{ runCubeM :: WriterT [Route] Id a }
|
| 114 |
|
| 115 |
instance Functor CubeM where
|
| 116 |
fmap f (CubeM x) = CubeM (fmap f x)
|
| 117 |
|
| 118 |
instance Applicative CubeM where
|
| 119 |
pure x = CubeM (pure x)
|
| 120 |
CubeM f <*> CubeM x = CubeM (f <*> x)
|
| 121 |
|
| 122 |
instance Monad CubeM where
|
| 123 |
CubeM x >>= f = CubeM (x >>= runCubeM . f)
|
| 124 |
|
| 125 |
type Env = [(ByteString, ByteString)]
|
| 126 |
type Writer = Maybe ByteString -> IO ()
|
| 127 |
|
| 128 |
newtype ActionM a = ActionM
|
| 129 |
{ runActionM :: ReaderT (Env, Writer) IO a }
|
| 130 |
|
| 131 |
runActionWith :: Env -> ActionM () -> Writer -> IO ()
|
| 132 |
runActionWith env action writer =
|
| 133 |
runReaderT (env, writer) (runActionM action)
|
| 134 |
|
| 135 |
instance Functor ActionM where
|
| 136 |
fmap f (ActionM x) = ActionM (fmap f x)
|
| 137 |
|
| 138 |
instance Applicative ActionM where
|
| 139 |
pure x = ActionM (pure x)
|
| 140 |
ActionM f <*> ActionM x = ActionM (f <*> x)
|
| 141 |
|
| 142 |
instance Monad ActionM where
|
| 143 |
ActionM x >>= f = ActionM (x >>= runActionM . f)
|
| 144 |
|
| 145 |
instance MonadIO ActionM where
|
| 146 |
liftIO mote = ActionM (inBase mote)
|