]> gitweb.michael.orlitzky.com - hath.git/blob - src/Main.hs
f567400c1ac4080905b9a31700051bf91cb81d82
[hath.git] / src / Main.hs
1 module Main
2 where
3
4 import Control.Concurrent.ParallelIO.Global (
5 parallel,
6 stopGlobalPool )
7 import Control.Monad (unless, when)
8 import qualified Data.ByteString.Char8 as BS (intercalate, pack, unpack)
9 import Data.List ((\\), intercalate)
10 import Data.Maybe (catMaybes, isNothing)
11 import Data.String.Utils (splitWs)
12 import System.Exit (ExitCode(..), exitSuccess, exitWith)
13 import System.IO (stderr, hPutStrLn)
14 import Text.Read (readMaybe)
15
16 import Cidr (
17 Cidr(..),
18 combine_all,
19 enumerate,
20 max_octet1,
21 max_octet2,
22 max_octet3,
23 max_octet4,
24 min_octet1,
25 min_octet2,
26 min_octet3,
27 min_octet4 )
28 import CommandLine (
29 help_set,
30 help_text,
31 input_function,
32 Mode(..),
33 parse_errors,
34 parse_mode )
35 import DNS (Domain, lookup_ptrs)
36 import ExitCodes ( exit_args_parse_failed, exit_invalid_cidr )
37 import Octet ()
38
39
40 -- | A regular expression that matches a non-address character.
41 non_addr_char :: String
42 non_addr_char = "[^\\.0-9]"
43
44
45 -- | Add non_addr_chars on either side of the given String. This
46 -- prevents (for example) the regex '127.0.0.1' from matching
47 -- '127.0.0.100'.
48 addr_barrier :: String -> String
49 addr_barrier x = non_addr_char ++ x ++ non_addr_char
50
51
52 -- | The magic happens here. We take a CIDR String as an argument, and
53 -- return the equivalent regular expression. We do this as follows:
54 --
55 -- 1. Compute the minimum possible value of each octet.
56 -- 2. Compute the maximum possible value of each octet.
57 -- 3. Generate a regex matching every value between those min and
58 -- max values.
59 -- 4. Join the regexes from step 3 with regexes matching periods.
60 -- 5. Stick an address boundary on either side of the result.
61 cidr_to_regex :: Cidr.Cidr -> String
62 cidr_to_regex cidr =
63 addr_barrier (intercalate "\\." [range1, range2, range3, range4])
64 where
65 range1 = numeric_range min1 max1
66 range2 = numeric_range min2 max2
67 range3 = numeric_range min3 max3
68 range4 = numeric_range min4 max4
69 min1 = fromEnum (min_octet1 cidr)
70 min2 = fromEnum (min_octet2 cidr)
71 min3 = fromEnum (min_octet3 cidr)
72 min4 = fromEnum (min_octet4 cidr)
73 max1 = fromEnum (max_octet1 cidr)
74 max2 = fromEnum (max_octet2 cidr)
75 max3 = fromEnum (max_octet3 cidr)
76 max4 = fromEnum (max_octet4 cidr)
77
78
79
80 -- | Take a list of Strings, and return a regular expression matching
81 -- any of them.
82 alternate :: [String] -> String
83 alternate terms = "(" ++ (intercalate "|" terms) ++ ")"
84
85
86 -- | Take two Ints as parameters, and return a regex matching any
87 -- integer between them (inclusive).
88 numeric_range :: Int -> Int -> String
89 numeric_range x y =
90 alternate (map show [lower..upper])
91 where
92 lower = minimum [x,y]
93 upper = maximum [x,y]
94
95
96 main :: IO ()
97 main = do
98 -- First, check for any errors that occurred while parsing
99 -- the command line options.
100 errors <- CommandLine.parse_errors
101 unless (null errors) $ do
102 hPutStrLn stderr (concat errors)
103 putStrLn CommandLine.help_text
104 exitWith (ExitFailure exit_args_parse_failed)
105
106 -- Next, check to see if the 'help' option was passed to the
107 -- program. If it was, display the help, and exit successfully.
108 help_opt_set <- CommandLine.help_set
109 when help_opt_set $ do
110 putStrLn CommandLine.help_text
111 exitSuccess
112
113 -- The input function we receive here should know what to read.
114 inputfunc <- (CommandLine.input_function)
115 input <- inputfunc
116
117 let cidr_strings = splitWs input
118 let cidrs = map readMaybe cidr_strings
119
120 when (any isNothing cidrs) $ do
121 putStrLn "Error: not valid CIDR notation."
122 exitWith (ExitFailure exit_invalid_cidr)
123
124 -- Filter out only the valid ones.
125 let valid_cidrs = catMaybes cidrs
126
127 -- Get the mode of operation.
128 mode <- CommandLine.parse_mode
129
130 case mode of
131 Regex -> do
132 let regexes = map cidr_to_regex valid_cidrs
133 putStrLn $ alternate regexes
134 Reduce ->
135 mapM_ print (combine_all valid_cidrs)
136 Dupe ->
137 mapM_ print dupes
138 where
139 dupes = valid_cidrs \\ (combine_all valid_cidrs)
140 Diff -> do
141 mapM_ putStrLn deletions
142 mapM_ putStrLn additions
143 where
144 dupes = valid_cidrs \\ (combine_all valid_cidrs)
145 deletions = map (\s -> '-' : (show s)) dupes
146 newcidrs = (combine_all valid_cidrs) \\ valid_cidrs
147 additions = map (\s -> '+' : (show s)) newcidrs
148 List -> do
149 let combined_cidrs = combine_all valid_cidrs
150 let addrs = concatMap enumerate combined_cidrs
151 mapM_ print addrs
152 Reverse -> do
153 let combined_cidrs = combine_all valid_cidrs
154 let addrs = concatMap enumerate combined_cidrs
155 let addr_bytestrings = map (BS.pack . show) addrs
156 ptrs <- lookup_ptrs addr_bytestrings
157 let pairs = zip addr_bytestrings ptrs
158 _ <- parallel (map (putStrLn . show_pair) pairs)
159 return ()
160
161 stopGlobalPool
162
163 where
164 show_pair :: (Domain, Maybe [Domain]) -> String
165 show_pair (s, mds) =
166 (BS.unpack s) ++ ": " ++ results
167 where
168 space = BS.pack " "
169 results =
170 case mds of
171 Nothing -> ""
172 Just ds -> BS.unpack $ BS.intercalate space ds