#!/home/spc/web/Lua/bin/lua -- ************************************************************************ -- -- Main program -- Copyright 2018 by Sean Conner. All Rights Reserved. -- -- This program is free software: you can redistribute it and/or modify -- it under the terms of the GNU General Public License as published by -- the Free Software Foundation, either version 3 of the License, or -- (at your option) any later version. -- -- This program is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU General Public License for more details. -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see . -- -- Comments, questions and criticisms can be sent to: sean@conman.org -- -- ************************************************************************ -- luacheck: ignore 611 -- RFC-3875 -- RFC-8484 -- https://daniel.haxx.se/blog/2018/06/03/inside-firefoxs-doh-engine/ local syslog = require "org.conman.syslog" local dns = require "org.conman.dns" local base64 = require "org.conman.base64"() local httpret = require "org.conman.const.http-response" local lpeg = require "lpeg" -- ********************************************************************** local function output(status,data) if not data then io.stdout:write(string.format([[ Status: %d Content-Length: 0 ]],status)) else io.stdout:write(string.format([[ Status: %d Content-Type: application/dns-message Content-Length: %d %s]],status,#data,data)) end end -- ********************************************************************** local function dns_query(query) local q = dns.decode(query) if q then syslog( 'notice', "%s host=%q question.type=%s rc=%d age=%d", "POST", q.question.name, q.question.type, 0, 0 ) end local reply = dns.query('127.0.0.1',query) if not reply then output(httpret.CLIENT.TIMEOUT) else output(httpret.SUCCESS.OKAY,reply) end end -- ********************************************************************** local query_string do local xdigit = lpeg.locale().xdigit local char = lpeg.P"%" * lpeg.C(xdigit * xdigit) / function(c) return string.char(tonumber(c,16)) end + lpeg.R"!~" local name = lpeg.Cs((char - (lpeg.P"=" + lpeg.P"&"))^1) local value = lpeg.P"=" * lpeg.Cs((char - lpeg.P"&")^1) + lpeg.Cc"" query_string = lpeg.Cf( lpeg.Ct"" * lpeg.Cg(name * value * lpeg.P"&"^-1)^0, function(result,n,v) if result and not result[n] then result[n] = v return result else return false end end ) end -- ********************************************************************** local function http_get() local vars = query_string:match(os.getenv "QUERY_STRING") if vars and vars['dns'] then dns_query(base64:decode(vars['dns'])) else output(httpret.CLIENT.BADREQ) end end -- ********************************************************************** local function http_post() if os.getenv("CONTENT_TYPE") ~= "application/dns-message" then output(httpret.CLIENT.MEDIATYPE) return end local len = tonumber(os.getenv("CONTENT_LENGTH")) if not len then output(httpret.CLIENT.LENGTHREQ) return end dns_query(io.stdin:read(len)) end -- ********************************************************************** syslog.open("DNSoverHTTP/2-3","user") local method = os.getenv("REQUEST_METHOD") if method == "POST" then http_post() elseif method == "GET" then http_get() else output(httpret.SERVER.NOTIMP) -- per RFC-3875 s6.3.3 end os.exit(0,true)