packages feed

iptables-helpers-0.5.0: src/Iptables/Print.hs

module Iptables.Print where

import Codec.Binary.UTF8.String
import Data.Bits
import Data.List
import Data.Set (toList)
import Data.Word
import Iptables.Types
import Numeric
import Safe

printIptables :: Iptables -> String
printIptables (Iptables f n m r) =
    (if null f then "" else printTable "filter" f)
    ++ (if null n then "" else printTable "nat" n)
    ++ (if null m then "" else printTable "mangle" m)
    ++ (if null r then "" else printTable "raw" r)
    where
        printTable :: String -> [Chain] -> String
        printTable tableName chains =
            "*" ++ tableName ++ "\n"
            ++ unlines (map printChainCaption chains)
            ++ concat (map printChain chains)
            ++ "COMMIT\n"

        printChainCaption :: Chain -> String
        printChainCaption (Chain name policy counters _) =
            ":" ++ name ++ " "
            ++ printPolicy policy ++ " "
            ++ printCounters counters

        printPolicy :: Policy -> String
        printPolicy p = case p of
            ACCEPT -> "ACCEPT"
            DROP -> "DROP"
            PUNDEFINED -> "-"

printChain :: Chain -> String
printChain (Chain name _ _ rules) =
    unlines (map (printRule name) rules)

printCounters :: Counters -> String
printCounters (Counters a b) = "[" ++ show a ++ ":" ++ show b ++ "]"

printRuleForRun :: Rule -> String
printRuleForRun (Rule _ ruleOpts target) =
    unwords (map printOption ruleOpts) ++ " "
    ++ printTarget target

printRule :: String -> Rule -> String
printRule chainName (Rule counters ruleOpts target) =
    printCounters counters ++ " "
    ++ "-A " ++ chainName ++ " "
    ++ unwords (map printOption ruleOpts) ++ " "
    ++ printTarget target

printOption :: RuleOption -> String
printOption opt = case opt of
    (OProtocol b p) -> unwords $ printInv b ++ ["-p"] ++ [p]
    (OSource b addr) -> unwords $ printInv b ++ ["-s"] ++ [printAddress addr]
    (ODest b addr) -> unwords $ printInv b ++ ["-d"] ++ [printAddress addr]
    (OInInt b int) -> unwords $ printInv b ++ ["-i"] ++ [printInterface int]
    (OOutInt b int) -> unwords $ printInv b ++ ["-o"] ++ [printInterface int]
    (OSourcePort b p) -> unwords $ printInv b ++ ["--sport"] ++ [printPort p]
    (ODestPort b p) -> unwords $ printInv b ++ ["--dport"] ++ [printPort p]
    (OModule m) -> unwords $ "-m" : [printModule m]
    (OMacSource b m) -> unwords $ printInv b ++ ["--mac-source"] ++ [printMacAddress m]
    (OState s) -> unwords $ "--state" : [printStates $ toList s]
    (OPhysDevIsBridged b) -> unwords $ printInv b ++ ["--physdev-is-bridged"]
    (OComment c) -> "--comment" ++ " " ++ printComment c
    (OUnknown oName b opts) -> unwords $ printInv b ++ [oName] ++ opts

printInv :: Bool -> [String]
printInv True = []
printInv False = ["!"]

printTarget :: RuleTarget -> String
printTarget rt = (++) "-j " $ case rt of
                            TAccept -> "ACCEPT"
                            TDrop -> "DROP"
                            TReject rw -> "REJECT" ++ " --reject-with " ++ printRejectWith rw
                            TReturn -> "RETURN"
                            TUChain chain -> chain
                            TSNat natAddr rand persist ->
                                let randS = if rand then " --random"
                                                    else ""
                                    persistS = if persist then " --persistent"
                                                           else ""
                                in
                                    "SNAT " ++ "--to-source " ++ printNatAddr natAddr ++ randS ++ persistS
                            TDNat natAddr rand persist ->
                                let randS = if rand then " --random"
                                                    else ""
                                    persistS = if persist then " --persistent"
                                                          else ""
                                in
                                    "DNAT " ++ "--to-destination " ++ printNatAddr natAddr ++ randS ++ persistS
                            TMasquerade natPort rand ->
                                let randS = if rand then " --random"
                                                    else ""
                                    natPortS = case natPort of
                                        NatPortDefault -> ""
                                        _ -> " --to-ports " ++ printNatPort natPort
                                in
                                    "MASQUERADE" ++ natPortS ++ randS
                            TRedirect natPort rand ->
                                let randS = if rand then " --random"
                                                    else ""
                                    natPortS = case natPort of
                                        NatPortDefault -> ""
                                        _ -> " --to-ports " ++ printNatPort natPort
                                in
                                    "REDIRECT" ++ natPortS ++ randS
                            TUnknown tName opts ->
                                tName ++ " " ++ unwords opts

printAddress :: Addr -> String
printAddress (AddrIP ip) = printIp ip
printAddress (AddrMask ip mask) = printIp ip ++ "/" ++ printIp mask
printAddress (AddrPref ip pref) = printIp ip ++ "/" ++ show pref

printIp :: Word32 -> String
printIp ip =
    let oct1 = show $ shiftR ip 24
        oct2 = show $ shiftR (shiftL ip 8) 24
        oct3 = show $ shiftR (shiftL ip 16) 24
        oct4 = show $ shiftR (shiftL ip 24) 24
    in oct1 ++ "." ++ oct2 ++ "." ++ oct3 ++ "." ++ oct4

printMacAddress :: MacAddr -> String
printMacAddress (MacAddr a b c d e f) = showHex2 a $ showChar ':'
                                      $ showHex2 b $ showChar ':'
                                      $ showHex2 c $ showChar ':'
                                      $ showHex2 d $ showChar ':'
                                      $ showHex2 e $ showChar ':'
                                      $ showHex2 f $ ""
    where
        showHex2 :: Word8 -> ShowS
        showHex2 a = if a < 16 then showChar '0' . showHex a
                               else showHex a

printInterface :: Interface -> String
printInterface (Interface str) = str

printPort :: Port -> String
printPort (Port ps) = intercalate "," $ map show ps
printPort (PortRange ps pe) = show ps ++ ":" ++ show pe

printStates :: [CState] -> String
printStates ss = intercalate "," $ map printState ss
    where
    printState st = case st of
            CStInvalid -> "INVALID"
            CStEstablished -> "ESTABLISHED"
            CStNew -> "NEW"
            CStRelated -> "RELATED"
            CStUntracked -> "UNTRACKED"

printModule :: Module -> String
printModule m = case m of
        ModTcp -> "tcp"
        ModUdp -> "udp"
        ModLimit -> "limit"
        ModMac -> "mac"
        ModMark -> "mark"
        ModMultiport -> "multiport"
        ModOwner -> "owner"
        ModState -> "state"
        ModTos -> "tos"
        ModTtl -> "ttl"
        ModPhysDev -> "physdev"
        ModComment -> "comment"
        ModOther s -> s

printNatAddr :: NatAddress -> String
printNatAddr (NAIp ip1 ip2) = printNatIp ip1 ip2
printNatAddr (NAIpPort ip1 ip2 port1 port2) = printNatIpPort ip1 ip2 port1 port2

printNatIp :: Word32 -> Word32 -> String
printNatIp ip1 ip2 =
    if ip1 == ip2 then printIp ip1
                  else printIp ip1 ++ "-" ++ printIp ip2

printNatIpPort :: Word32 -> Word32 -> Int -> Int -> String
printNatIpPort ip1 ip2 port1 port2 =
    let ipString = if ip1 == ip2 then printIp ip1
                                else printIp ip1 ++ "-" ++ printIp ip2
        portString = if port1 == port2 then show port1
                                       else show port1 ++ "-" ++ show port2
    in
        ipString ++ ":" ++ portString

printNatPort :: NatPort -> String
printNatPort NatPortDefault = ""
printNatPort (NatPort port1 port2) =
    if port1 == port2
        then show port1
        else show port1 ++ "-" ++ show port2

printRejectWith :: RejectType -> String
printRejectWith rw = case rw of
    RTNetUnreachable -> "icmp-net-unreachable"
    RTHostUnreachable -> "icmp-host-unreachable"
    RTPortUnreachable -> "icmp-port-unreachable"
    RTProtoUnreachable -> "icmp-proto-unreachable"
    RTNetProhibited -> "icmp-net-prohibited"
    RTHostProhibited -> "icmp-host-prohibited"
    RTAdminProhibited -> "icmp-admin-prohibited"
    RTTcpReset -> "tcp-reset"

-- | Iptables doesn't work correctly with russian chars.
-- It's working only if a comment is enclosed with single quotes and it doesn't include spaces.
-- Let's assume that this happens with all multibyte chars.
printComment :: String -> String
printComment com =
    let onlyOneByteChars :: String -> Bool
        onlyOneByteChars [] = True
        onlyOneByteChars (x:xs) = if length (encodeChar x) > 1 then True
                                                    else onlyOneByteChars xs
    in
    if onlyOneByteChars com
        then "\"" ++ com ++ "\""
        else 
            let com' = if headMay com == Just '\'' then com
                                                   else "'" ++ com ++ "'"
                com'' = if null (filter (== ' ') com') then com'
                                                      else (map (\a -> if a == ' ' then '_' else a) com')
            in com''