iptadmin-1.0.0: src/IptAdmin/EditForm.hs
module IptAdmin.EditForm where
import Control.Monad.State
import Data.Either
import Data.Maybe
import Data.Set (size)
import IptAdmin.EditForm.Class
import IptAdmin.EditForm.Types
import IptAdmin.Utils
import Iptables
import Iptables.Parser
import Iptables.Types
import Text.ParserCombinators.Parsec.Prim hiding (State (..))
{- | По набору параметров формы получаем набор сообщений об ошибках,
- либо набор сообщений и правило.
- Функция возвращает набор сообщений, которые будут показаны в вебинтерфейсе, либо
- если ошибок нет, то правило + набор сообщений, которые будут показаны в интерфейсе.
- Сделано так потому, что в интерфейсе нужно отобразить статус проверки в любом случае,
- даже если введённые данные полностью корректны
-
- Кроме этого необходимо отобразить статус проверки для каждого параметра, для этого тип FormError
- проходит по всем проверкам и остаётся на выходе
-
- Сначала выполняем парсинг всех полей ввода,
- затем для каждой опции выполняем логическую проверку правила
-}
editPageProcessParams :: EditForm a => a -> Either [ResMessage] ([RuleOption], RuleTarget, [ResMessage])
editPageProcessParams form =
let entryList = toEntryList form
-- Парсим список пунктов формы
parseResList = map parseFormEntry entryList
{- Для каждого элемента результата парсинга проверяем наличие необходимого модуля iptables,
- для этого передаём список RuleMatch
-}
parseResList' = map (checkMatch $ lefts $ mapMaybe fst parseResList) parseResList
{- TODO: проверка зависимости действий от параметров. Например, в -xNAT можно указывать порты
- только в том случае, если есть опция -p udp или -p tcp
-}
isThereError :: [(a, ResMessage)] -> Bool
isThereError resList =
let errors = filter isError resList
isError :: (a, ResMessage) -> Bool
isError (_, RMError _) = True
isError _ = False
in not $ null errors
in
if isThereError parseResList' then
Left $ map snd parseResList'
else
Right $ (,,) (lefts $ mapMaybe fst parseResList')
(head $ rights $ mapMaybe fst parseResList')
(map snd parseResList')
{- | Обработка параметров формы.
- На входе параметр формы,
- на выходе (опция или действие) или ничего и сообщение о результатах обработки параметра формы
-}
parseFormEntry :: FormEntry -> (Maybe (Either RuleOption RuleTarget), ResMessage)
parseFormEntry entry =
case entry of
(FESrc en inv str) ->
if en
then
let addrE = parse (pWrapper ipAddressParser) "src" str
in
case addrE of
Left _ -> (Nothing, RMError "parsing error")
Right addr -> (Just $ Left $ OSource (not inv) addr, RMSucc "")
else
(Nothing, RMIgnore)
(FEDst en inv str) ->
if en
then
let addrE = parse (pWrapper ipAddressParser) "dst" str
in
case addrE of
Left _ -> (Nothing, RMError "parsing error")
Right addr -> (Just $ Left $ ODest (not inv) addr, RMSucc "")
else
(Nothing, RMIgnore)
(FEProt en inv prot) ->
if en
then
case prot of
FTCP -> (Just $ Left $ OProtocol (not inv) "tcp", RMSucc "")
FUDP -> (Just $ Left $ OProtocol (not inv) "udp", RMSucc "")
FICMP -> (Just $ Left $ OProtocol (not inv) "icmp", RMSucc "")
else
(Nothing, RMIgnore)
(FESPort en inv str) ->
if en
then
let portE = parse (pWrapper ipPortParser) "source port" str
in
case portE of
Left _ -> (Nothing, RMError "parsing error")
Right port -> (Just $ Left $ OSourcePort (not inv) port, RMSucc "")
else
(Nothing, RMIgnore)
(FEDPort en inv str) ->
if en
then
let portE = parse (pWrapper ipPortParser) "dest port" str
in
case portE of
Left _ -> (Nothing, RMError "parsing error")
Right port -> (Just $ Left $ ODestPort (not inv) port, RMSucc "")
else
(Nothing, RMIgnore)
(FEInput en inv str) ->
if en
then
let interfaceE = parse (pWrapper interfaceParser) "input interface" str
in
case interfaceE of
Left _ -> (Nothing, RMError "parsing error")
Right interface -> (Just $ Left $ OInInt (not inv) $ Interface interface, RMSucc "")
else
(Nothing, RMIgnore)
(FEOutput en inv str) ->
if en
then
let interfaceE = parse (pWrapper interfaceParser) "output interface" str
in
case interfaceE of
Left _ -> (Nothing, RMError "parsing error")
Right interface -> (Just $ Left $ OOutInt (not inv) $ Interface interface, RMSucc "")
else
(Nothing, RMIgnore)
(FEState en stateSet) ->
if en
then
if size stateSet == 0
then (Nothing, RMError "select states, please")
else (Just $ Left $ OState stateSet, RMSucc "")
else
(Nothing, RMIgnore)
(FEFiltTar ft rejectType userChain) ->
case ft of
FAccept -> (Just $ Right TAccept, RMSucc "")
FDrop -> (Just $ Right TDrop, RMSucc "")
FReject -> (Just $ Right $ TReject rejectType, RMSucc "")
FFUserChain -> parseUserChainEntry userChain
(FENatPrerOutTar ft dnataddr dnatrand dnatpersist redirport redirrand userChain) ->
case ft of
FDNat -> parseDNatEntry dnataddr dnatrand dnatpersist
FRedirect -> parseRedirectEntry redirport redirrand
FNPrerUserChain -> parseUserChainEntry userChain
(FENatPostrTar ft snataddr snatrand snatpersist masqport masqrand userChain) ->
case ft of
FMasq -> parseMasqEntry masqport masqrand
FSNat -> parseSNatEntry snataddr snatrand snatpersist
FNPostrUserChain -> parseUserChainEntry userChain
(FENatUserTar ft dnatAddr dnatRand dnatPersist redirPort redirRand
snatAddr snatRand snatPersist masqPort masqRand userChain) ->
case ft of
FUDNat -> parseDNatEntry dnatAddr dnatRand dnatPersist
FURedirect -> parseRedirectEntry redirPort redirRand
FUSNat -> parseSNatEntry snatAddr snatRand snatPersist
FUMasq -> parseMasqEntry masqPort masqRand
FNUserUserChain -> parseUserChainEntry userChain
a -> (Nothing, RMError $ "Unknown form entry: " ++ show a)
parseDNatEntry :: String -> Bool -> Bool -> (Maybe (Either RuleOption RuleTarget), ResMessage)
parseDNatEntry dnatAddr dnatRand dnatPersist =
let dnatAddrE = parse (pWrapper natAddrParser) "dnat address" dnatAddr
in case dnatAddrE of
Left _ -> (Nothing, RMError "parsing error")
Right dnatAddr' -> (Just $ Right $ TDNat dnatAddr' dnatRand dnatPersist, RMSucc "")
parseRedirectEntry :: String -> Bool -> (Maybe (Either RuleOption RuleTarget), ResMessage)
parseRedirectEntry redirPort redirRand =
let redirPortE = parse (pWrapper natPortParser) "redirect port" redirPort
in case redirPortE of
Left _ -> (Nothing, RMError "parsing error")
Right redirPort' -> (Just $ Right $ TRedirect redirPort' redirRand, RMSucc "")
parseSNatEntry :: String -> Bool -> Bool -> (Maybe (Either RuleOption RuleTarget), ResMessage)
parseSNatEntry snatAddr snatRand snatPersist =
let snatAddrE = parse (pWrapper natAddrParser) "snat address" snatAddr
in case snatAddrE of
Left _ -> (Nothing, RMError "parsing error")
Right snatAddr' -> (Just $ Right $ TSNat snatAddr' snatRand snatPersist, RMSucc "")
parseMasqEntry :: String -> Bool -> (Maybe (Either RuleOption RuleTarget), ResMessage)
parseMasqEntry masqPort masqRand =
let masqPortE =
if masqPort == "" then Right NatPortDefault
else parse (pWrapper natPortParser) "masquerade port" masqPort
in
case masqPortE of
Left _ -> (Nothing, RMError "parsing error")
Right masqPort' -> (Just $ Right $ TMasquerade masqPort' masqRand, RMSucc "")
parseUserChainEntry :: String -> (Maybe (Either RuleOption RuleTarget), ResMessage)
parseUserChainEntry userChain = (Just $ Right $ TUChain userChain, RMSucc "")
{- | Проверка зависимостей опции и таргета от других опций
- на входе список всех распарсенных опций и результат обработки пункта формы
-}
checkMatch :: [RuleOption]
-> (Maybe (Either RuleOption RuleTarget), ResMessage)
-> (Maybe (Either RuleOption RuleTarget), ResMessage)
checkMatch opts (entry, mes) =
let isItTcp = not $ null $ filter (OProtocol True "tcp" ==) opts
isItUdp = not $ null $ filter (OProtocol True "udp" ==) opts
in
case entry of
Just (Left (OSourcePort _ _)) ->
if isItTcp || isItUdp
then (entry, mes)
else (Nothing, RMError "please select tcp or udp protocol")
Just (Left (ODestPort _ _)) ->
if isItTcp || isItUdp
then (entry, mes)
else (Nothing, RMError "please select tcp or udp protocol")
Just (Right (TReject RTTcpReset)) ->
if isItTcp
then (entry, mes)
else (Nothing, RMError "the parameter works only with tcp protocol")
Just (Right (TSNat (NAIpPort _ _ _ _) _ _)) ->
if isItTcp || isItUdp
then (entry, mes)
else (Nothing, RMError "please select tcp or udp protocol")
Just (Right (TDNat (NAIpPort _ _ _ _) _ _)) ->
if isItTcp || isItUdp
then (entry, mes)
else (Nothing, RMError "please select tcp or udp protocol")
Just (Right (TMasquerade (NatPort _ _) _)) ->
if isItTcp || isItUdp
then (entry, mes)
else (Nothing, RMError "please select tcp or udp protocol")
Just (Right (TRedirect (NatPort _ _) _)) ->
if isItTcp || isItUdp
then (entry, mes)
else (Nothing, RMError "please select tcp or udp protocol")
a -> (a, mes)
{- | Для каждой опции получает список модулей, от которых она зависит
- затем для каждого модуля проверяет, есть ли он уже в правиле
- и если его там нет, то добавляет
- В состоянии передаётся список опций правила
- вызывать так: options' <- runState (mapM_ completeModules options) options
-}
completeModules :: RuleOption -> State [RuleOption] ()
completeModules opt = do
let depends = optionDepends opt
mapM_ addModule depends
where
addModule :: Module -> State [RuleOption] ()
addModule m = do
matches <- get
let mod' = filter (isModule m) matches
when (null mod') $
put $ OModule m : matches
isModule :: Module -> RuleOption -> Bool
isModule m ro | OModule m == ro = True
| otherwise = False