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