-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpdns-pipe-nmc.hs
207 lines (177 loc) · 6.76 KB
/
pdns-pipe-nmc.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Prelude hiding (lookup, readFile)
import System.Environment
import System.Console.GetOpt
import System.IO hiding (readFile)
import System.IO.Error
import Data.Time.Clock.POSIX
import Control.Exception
import Text.Show.Pretty hiding (String)
import Control.Monad
import Control.Monad.State
import Data.ByteString.Lazy hiding (reverse, putStr, putStrLn, head, empty)
import qualified Data.ByteString.Char8 as C (pack)
import qualified Data.ByteString.Lazy.Char8 as L (pack)
import qualified Data.Text as T (pack)
import Data.List.Split
import Data.Map.Lazy (Map, empty, lookup, insert, delete, size)
import Data.Aeson (encode, decode, Value(..))
import Network.HTTP.Types
import Network.HTTP.Client
#if MIN_VERSION_http_client(0,3,0)
import Data.Default.Class (def)
#else
import Data.Default (def)
#endif
import JsonRpcClient
import Config
import PowerDns
import NmcRpc
import NmcDom
import NmcTransform
confFile = "/etc/namecoin.conf"
-- HTTP/JsonRpc interface
qReq :: Config -> String -> Int -> Request
qReq cf q id = applyBasicAuth (C.pack (rpcuser cf)) (C.pack (rpcpassword cf))
$ def { host = (C.pack (rpchost cf))
, port = (rpcport cf)
, method = "PUT"
, requestHeaders = [ (hAccept, "application/json")
, (hContentType, "application/json")
, (hConnection, "Keep-Alive")
]
, requestBody = RequestBodyLBS $ encode $
JsonRpcRequest JsonRpcV1
"name_show"
[L.pack q]
(String (T.pack (show id)))
, checkStatus = \_ _ _ -> Nothing
}
qRsp :: Response ByteString -> Either String ByteString
qRsp rsp =
case parseJsonRpc (responseBody rsp) :: Either JsonRpcError NmcRes of
Left jerr ->
case (jrpcErrCode jerr) of
(-4) -> Right "{}" -- this is how non-existent entry is returned
_ -> Left $ "JsonRpc error response: " ++ (show jerr)
Right jrsp -> Right $ resValue jrsp
-- NMC interface
queryOpNmc cfg mgr qid key =
httpLbs (qReq cfg key qid) mgr >>= return . qRsp
queryOpFile key = catch (readFile key >>= return . Right)
(\e -> return (Left (show (e :: IOException))))
queryDom queryOp fqdn =
case reverse (splitOn "." fqdn) of
"bit":dn:xs -> descendNmcDom queryOp xs $ seedNmcDom dn
_ -> return $ Left "Only \".bit\" domain is supported"
-- Number of ten minute intervals elapsed since creation of Namecoin
-- on April 18, 2011. Another option would be to use blockcount
-- but that would require another lookup, and we are cheap.
-- Yet another - to use (const - expires_in) from the lookup.
nmcAge = fmap (\x -> floor ((x - 1303070400) / 600)) getPOSIXTime
-- run a PowerDNS coprocess. Negotiate ABI version and execute requests.
mainPdnsNmc = do
cfg <- readConfig confFile
hSetBuffering stdin LineBuffering
hSetBuffering stdout LineBuffering
ver <- do
let
loopErr e = forever $ do
putStrLn $ "FAIL\t" ++ e
_ <- getLine
return ()
s <- getLine
case words s of
["HELO", "1"] -> return 1
["HELO", "2"] -> return 2
["HELO", "3"] -> return 3
["HELO", "4"] -> return 4
["HELO", x ] -> loopErr $ "unsupported ABI version " ++ (show x)
_ -> loopErr $ "bad HELO " ++ (show s)
putStrLn $ "OK\tDnsNmc ready to serve, protocol v." ++ (show ver)
mgr <- newManager defaultManagerSettings
let
fetch = lookup
-- Save the name under current count, increment count for the next run
-- so the name is saved under the count that was put into the response.
stow name (count, cache) =
(if count >= 99 then 0 else count + 1
, insert count name
$ delete (if count >= 10 then count - 10 else count + 90) cache
)
io = liftIO
mainloop = forever $ do
l <- io getLine
gen <- io $ nmcAge
(count, cache) <- get
case pdnsParse ver l of
Left e -> io $ putStr $ pdnsReport e
Right preq -> do
case preq of
PdnsRequestQ qname qtype id _ _ _ -> do
io $ queryDom (queryOpNmc cfg mgr id) qname
>>= putStr . (pdnsOutQ ver count gen qname qtype)
{-
-- debug
io $ putStrLn $ "LOG\tRequest number " ++ (show count)
++ " id: " ++ (show id)
++ " qname: " ++ qname
++ " qtype: " ++ (show qtype)
++ " cache size: " ++ (show (size cache))
-- end debug
-}
put $ stow qname (count, cache)
PdnsRequestAXFR xrq zid -> do
{-
-- debug
io $ putStrLn $ "LOG\tAXFR request id=" ++ (show xrq)
++ ", zone name: " ++ (show zid)
-- end debug
-}
let
czone = fetch xrq cache
zone = case zid of
Nothing -> czone
Just qname -> Just qname
-- if zid == czone then zid else Nothing -- paranoid
case zone of
Just qname ->
io $ queryDom (queryOpNmc cfg mgr xrq) qname
>>= putStr . (pdnsOutXfr ver count gen qname)
Nothing ->
io $ putStr $ pdnsReport $ "AXFR cannot determine zone: "
++ (show xrq) ++ ", " ++ (show zid)
PdnsRequestPing -> io $ putStrLn "END"
runStateT mainloop (0, empty) >> return ()
-- helper for command-line tools
pdnsOut gen key qt dom =
case qt of
"AXFR" -> pdnsOutXfr 1 (-1) gen key dom
_ -> pdnsOutQ 1 (-1) gen key (rrType qt) dom
-- run one query by key from Namecoin, print domain object and answer
mainOne gen key qt = do
cfg <- readConfig confFile
mgr <- newManager defaultManagerSettings
dom <- queryDom (queryOpNmc cfg mgr (-1)) key
putStrLn $ ppShow dom
putStr $ pdnsOut gen key qt dom
-- get data from the file, print domain object and answer
mainFile gen key qt = do
dom <- queryDom queryOpFile key
putStrLn $ ppShow dom
putStr $ pdnsOut gen key qt dom
-- Entry point
main = do
args <- getArgs
gen <- nmcAge
let
with f xs = case xs of
[qfqdn, qtype] -> f gen qfqdn qtype
_ -> error $ "usage: empty args, or \"[-f] <fqdn> {<TYPE>|ANY|AXFR}\""
++ " (type in caps)"
case args of
[] -> mainPdnsNmc
"-f":xs -> with mainFile xs
_ -> with mainOne args