Mercurial > prosody-hg
comparison util/dns.lua @ 10961:f93dce30089a
util.dns: Library for decoding DNS records
Imported from luaunbound-prosody 5f7c771138b1
| author | Kim Alvefur <zash@zash.se> |
|---|---|
| date | Sat, 09 Mar 2019 21:16:27 +0100 |
| parents | |
| children | b3773b1b90a1 |
comparison
equal
deleted
inserted
replaced
| 10960:f84e0e2faae2 | 10961:f93dce30089a |
|---|---|
| 1 -- libunbound based net.adns replacement for Prosody IM | |
| 2 -- Copyright (C) 2012-2015 Kim Alvefur | |
| 3 -- Copyright (C) 2012 Waqas Hussain | |
| 4 -- | |
| 5 -- This file is MIT licensed. | |
| 6 | |
| 7 local setmetatable = setmetatable; | |
| 8 local table = table; | |
| 9 local t_concat = table.concat; | |
| 10 local t_insert = table.insert; | |
| 11 local s_byte = string.byte; | |
| 12 local s_char = string.char; | |
| 13 local s_format = string.format; | |
| 14 local s_gsub = string.gsub; | |
| 15 local s_sub = string.sub; | |
| 16 local s_match = string.match; | |
| 17 local s_gmatch = string.gmatch; | |
| 18 | |
| 19 local have_net, net_util = pcall(require, "util.net"); | |
| 20 | |
| 21 if have_net and not net_util.ntop then -- Added in Prosody 0.11 | |
| 22 have_net = false; | |
| 23 end | |
| 24 | |
| 25 local chartohex = {}; | |
| 26 | |
| 27 for c = 0, 255 do | |
| 28 chartohex[s_char(c)] = s_format("%02X", c); | |
| 29 end | |
| 30 | |
| 31 local function tohex(s) | |
| 32 return (s_gsub(s, ".", chartohex)); | |
| 33 end | |
| 34 | |
| 35 -- Converted from | |
| 36 -- http://www.iana.org/assignments/dns-parameters | |
| 37 -- 2015-05-19 | |
| 38 | |
| 39 local classes = { | |
| 40 IN = 1; "IN"; | |
| 41 nil; | |
| 42 CH = 3; "CH"; | |
| 43 HS = 4; "HS"; | |
| 44 }; | |
| 45 | |
| 46 local types = { | |
| 47 "A";"NS";"MD";"MF";"CNAME";"SOA";"MB";"MG";"MR";"NULL";"WKS";"PTR";"HINFO"; | |
| 48 "MINFO";"MX";"TXT";"RP";"AFSDB";"X25";"ISDN";"RT";"NSAP";"NSAP-PTR";"SIG"; | |
| 49 "KEY";"PX";"GPOS";"AAAA";"LOC";"NXT";"EID";"NIMLOC";"SRV";"ATMA";"NAPTR"; | |
| 50 "KX";"CERT";"A6";"DNAME";"SINK";"OPT";"APL";"DS";"SSHFP";"IPSECKEY";"RRSIG"; | |
| 51 "NSEC";"DNSKEY";"DHCID";"NSEC3";"NSEC3PARAM";"TLSA";[55]="HIP";[56]="NINFO"; | |
| 52 [57]="RKEY";[58]="TALINK";[59]="CDS";[60]="CDNSKEY";[61]="OPENPGPKEY"; | |
| 53 [62]="CSYNC";TLSA=52;NS=2;[249]="TKEY";[251]="IXFR";NSAP=22;UID=101;APL=42; | |
| 54 MG=8;NIMLOC=32;DHCID=49;TALINK=58;HINFO=13;MINFO=14;EID=31;DS=43;CSYNC=62; | |
| 55 RKEY=57;TKEY=249;NID=104;NAPTR=35;RT=21;LP=107;L32=105;KEY=25;MD=3;MX=15; | |
| 56 A6=38;KX=36;PX=26;CAA=257;WKS=11;TSIG=250;MAILA=254;CDS=59;SINK=40;LOC=29; | |
| 57 DLV=32769;[32769]="DLV";TA=32768;[32768]="TA";GID=102;IXFR=251;MAILB=253; | |
| 58 [256]="URI";[250]="TSIG";[252]="AXFR";NSEC=47;HIP=55;[254]="MAILA";[255]="*"; | |
| 59 NSEC3PARAM=51;["*"]=255;URI=256;[253]="MAILB";AXFR=252;SPF=99;NXT=30;AFSDB=18; | |
| 60 EUI48=108;NINFO=56;CDNSKEY=60;ISDN=20;L64=106;SRV=33;DNSKEY=48;X25=19;TXT=16; | |
| 61 RRSIG=46;OPENPGPKEY=61;DNAME=39;CNAME=5;EUI64=109;A=1;MR=9;IPSECKEY=45;OPT=41; | |
| 62 UNSPEC=103;["NSAP-PTR"]=23;[103]="UNSPEC";[257]="CAA";UINFO=100;[99]="SPF"; | |
| 63 MF=4;[101]="UID";[102]="GID";SOA=6;[104]="NID";[105]="L32";[106]="L64"; | |
| 64 [107]="LP";[108]="EUI48";[109]="EUI64";NSEC3=50;RP=17;PTR=12;[100]="UINFO"; | |
| 65 NULL=10;AAAA=28;MB=7;GPOS=27;SSHFP=44;CERT=37;SIG=24;ATMA=34 | |
| 66 }; | |
| 67 | |
| 68 local errors = { | |
| 69 NoError = "No Error"; [0] = "NoError"; | |
| 70 FormErr = "Format Error"; "FormErr"; | |
| 71 ServFail = "Server Failure"; "ServFail"; | |
| 72 NXDomain = "Non-Existent Domain"; "NXDomain"; | |
| 73 NotImp = "Not Implemented"; "NotImp"; | |
| 74 Refused = "Query Refused"; "Refused"; | |
| 75 YXDomain = "Name Exists when it should not"; "YXDomain"; | |
| 76 YXRRSet = "RR Set Exists when it should not"; "YXRRSet"; | |
| 77 NXRRSet = "RR Set that should exist does not"; "NXRRSet"; | |
| 78 NotAuth = "Server Not Authoritative for zone"; "NotAuth"; | |
| 79 NotZone = "Name not contained in zone"; "NotZone"; | |
| 80 }; | |
| 81 | |
| 82 -- Simplified versions of Waqas DNS parsers | |
| 83 -- Only the per RR parsers are needed and only feed a single RR | |
| 84 | |
| 85 local parsers = {}; | |
| 86 | |
| 87 -- No support for pointers, but libunbound appears to take care of that. | |
| 88 local function readDnsName(packet, pos) | |
| 89 if s_byte(packet, pos) == 0 then return "."; end | |
| 90 local pack_len, r, len = #packet, {}; | |
| 91 pos = pos or 1; | |
| 92 repeat | |
| 93 len = s_byte(packet, pos) or 0; | |
| 94 t_insert(r, s_sub(packet, pos + 1, pos + len)); | |
| 95 pos = pos + len + 1; | |
| 96 until len == 0 or pos >= pack_len; | |
| 97 return t_concat(r, "."), pos; | |
| 98 end | |
| 99 | |
| 100 -- These are just simple names. | |
| 101 parsers.CNAME = readDnsName; | |
| 102 parsers.NS = readDnsName | |
| 103 parsers.PTR = readDnsName; | |
| 104 | |
| 105 local soa_mt = { | |
| 106 __tostring = function(rr) | |
| 107 return s_format("%s %s %d %d %d %d %d", rr.mname, rr.rname, rr.serial, rr.refresh, rr.retry, rr.expire, rr.minimum); | |
| 108 end; | |
| 109 }; | |
| 110 function parsers.SOA(packet) | |
| 111 local mname, rname, offset; | |
| 112 | |
| 113 mname, offset = readDnsName(packet, 1); | |
| 114 rname, offset = readDnsName(packet, offset); | |
| 115 | |
| 116 -- Extract all the bytes of these fields in one call | |
| 117 local | |
| 118 s1, s2, s3, s4, -- serial | |
| 119 r1, r2, r3, r4, -- refresh | |
| 120 t1, t2, t3, t4, -- retry | |
| 121 e1, e2, e3, e4, -- expire | |
| 122 m1, m2, m3, m4 -- minimum | |
| 123 = s_byte(packet, offset, offset + 19); | |
| 124 | |
| 125 return setmetatable({ | |
| 126 mname = mname; | |
| 127 rname = rname; | |
| 128 serial = s1*0x1000000 + s2*0x10000 + s3*0x100 + s4; | |
| 129 refresh = r1*0x1000000 + r2*0x10000 + r3*0x100 + r4; | |
| 130 retry = t1*0x1000000 + t2*0x10000 + t3*0x100 + t4; | |
| 131 expire = e1*0x1000000 + e2*0x10000 + e3*0x100 + e4; | |
| 132 minimum = m1*0x1000000 + m2*0x10000 + m3*0x100 + m4; | |
| 133 }, soa_mt); | |
| 134 end | |
| 135 | |
| 136 function parsers.A(packet) | |
| 137 return s_format("%d.%d.%d.%d", s_byte(packet, 1, 4)); | |
| 138 end | |
| 139 | |
| 140 local aaaa = { nil, nil, nil, nil, nil, nil, nil, nil, }; | |
| 141 function parsers.AAAA(packet) | |
| 142 local hi, lo, ip, len, token; | |
| 143 for i = 1, 8 do | |
| 144 hi, lo = s_byte(packet, i * 2 - 1, i * 2); | |
| 145 aaaa[i] = s_format("%x", hi * 256 + lo); -- skips leading zeros | |
| 146 end | |
| 147 ip = t_concat(aaaa, ":", 1, 8); | |
| 148 len = (s_match(ip, "^0:[0:]+()") or 1) - 1; | |
| 149 for s in s_gmatch(ip, ":0:[0:]+") do | |
| 150 if len < #s then len, token = #s, s; end -- find longest sequence of zeros | |
| 151 end | |
| 152 return (s_gsub(ip, token or "^0:[0:]+", "::", 1)); | |
| 153 end | |
| 154 | |
| 155 if have_net then | |
| 156 parsers.A = net_util.ntop; | |
| 157 parsers.AAAA = net_util.ntop; | |
| 158 end | |
| 159 | |
| 160 local mx_mt = { | |
| 161 __tostring = function(rr) | |
| 162 return s_format("%d %s", rr.pref, rr.mx) | |
| 163 end | |
| 164 }; | |
| 165 function parsers.MX(packet) | |
| 166 local name = readDnsName(packet, 3); | |
| 167 local b1,b2 = s_byte(packet, 1, 2); | |
| 168 return setmetatable({ | |
| 169 pref = b1*256+b2; | |
| 170 mx = name; | |
| 171 }, mx_mt); | |
| 172 end | |
| 173 | |
| 174 local srv_mt = { | |
| 175 __tostring = function(rr) | |
| 176 return s_format("%d %d %d %s", rr.priority, rr.weight, rr.port, rr.target); | |
| 177 end | |
| 178 }; | |
| 179 function parsers.SRV(packet) | |
| 180 local name = readDnsName(packet, 7); | |
| 181 local b1, b2, b3, b4, b5, b6 = s_byte(packet, 1, 6); | |
| 182 return setmetatable({ | |
| 183 priority = b1*256+b2; | |
| 184 weight = b3*256+b4; | |
| 185 port = b5*256+b6; | |
| 186 target = name; | |
| 187 }, srv_mt); | |
| 188 end | |
| 189 | |
| 190 local txt_mt = { __tostring = t_concat }; | |
| 191 function parsers.TXT(packet) | |
| 192 local pack_len = #packet; | |
| 193 local r, pos, len = {}, 1; | |
| 194 repeat | |
| 195 len = s_byte(packet, pos) or 0; | |
| 196 t_insert(r, s_sub(packet, pos + 1, pos + len)); | |
| 197 pos = pos + len + 1; | |
| 198 until pos >= pack_len; | |
| 199 return setmetatable(r, txt_mt); | |
| 200 end | |
| 201 | |
| 202 parsers.SPF = parsers.TXT; | |
| 203 | |
| 204 -- Acronyms from RFC 7218 | |
| 205 local tlsa_usages = { | |
| 206 [0] = "PKIX-CA"; | |
| 207 [1] = "PKIX-EE"; | |
| 208 [2] = "DANE-TA"; | |
| 209 [3] = "DANE-EE"; | |
| 210 [255] = "PrivCert"; | |
| 211 }; | |
| 212 local tlsa_selectors = { | |
| 213 [0] = "Cert", | |
| 214 [1] = "SPKI", | |
| 215 [255] = "PrivSel", | |
| 216 }; | |
| 217 local tlsa_match_types = { | |
| 218 [0] = "Full", | |
| 219 [1] = "SHA2-256", | |
| 220 [2] = "SHA2-512", | |
| 221 [255] = "PrivMatch", | |
| 222 }; | |
| 223 local tlsa_mt = { | |
| 224 __tostring = function(rr) | |
| 225 return s_format("%s %s %s %s", | |
| 226 tlsa_usages[rr.use] or rr.use, | |
| 227 tlsa_selectors[rr.select] or rr.select, | |
| 228 tlsa_match_types[rr.match] or rr.match, | |
| 229 tohex(rr.data)); | |
| 230 end; | |
| 231 __index = { | |
| 232 getUsage = function(rr) return tlsa_usages[rr.use] end; | |
| 233 getSelector = function(rr) return tlsa_selectors[rr.select] end; | |
| 234 getMatchType = function(rr) return tlsa_match_types[rr.match] end; | |
| 235 } | |
| 236 }; | |
| 237 function parsers.TLSA(packet) | |
| 238 local use, select, match = s_byte(packet, 1,3); | |
| 239 return setmetatable({ | |
| 240 use = use; | |
| 241 select = select; | |
| 242 match = match; | |
| 243 data = s_sub(packet, 4); | |
| 244 }, tlsa_mt); | |
| 245 end | |
| 246 | |
| 247 local params = { | |
| 248 TLSA = { | |
| 249 use = tlsa_usages; | |
| 250 select = tlsa_selectors; | |
| 251 match = tlsa_match_types; | |
| 252 }; | |
| 253 }; | |
| 254 | |
| 255 local fallback_mt = { | |
| 256 __tostring = function(rr) | |
| 257 return s_format([[\# %d %s]], #rr.raw, tohex(rr.raw)); | |
| 258 end; | |
| 259 }; | |
| 260 local function fallback_parser(packet) | |
| 261 return setmetatable({ raw = packet },fallback_mt); | |
| 262 end | |
| 263 setmetatable(parsers, { __index = function() return fallback_parser end }); | |
| 264 | |
| 265 return { | |
| 266 parsers = parsers; | |
| 267 classes = classes; | |
| 268 types = types; | |
| 269 errors = errors; | |
| 270 params = params; | |
| 271 }; |
